关于XSS和CSRF,面试官更喜欢这样的回答!

「C++ 40 周年」主题征文大赛(有机会与C++之父现场交流!) 10w+人浏览 698人参与

这是我们前端最常见的两种攻击手段,也是面试中最常考的前端攻击。这篇文章我用最精炼、最优雅,也是面试官最喜欢的回答方式来讲解下 XSS 和 CSRF。

一、XSS(跨站脚本)

原理

攻击者把 恶意脚本 注入到受信任页面并被浏览器执行,脚本 利用页面的信任上下文(Cookies、localStorage、DOM)窃取数据或劫持会话。

常见类型

  • 反射型​(参数或路径直接反射到页面并执行)
  • 存储型​(恶意内容存储在服务器,其他用户访问时触发)
  • DOM-based​(客户端不安全的 DOM 操作导致执行,和服务器无关)

最小复现示例(不安全的后端 + 不安全的前端)

后端(Express — 危险示例)

// server.js(示例,仅演示不安全行为)
const express = require('express');
const app = express();

app.get('/search', (req, res) => {
  const q = req.query.q || '';
  // 直接把用户输入拼到 HTML 中 —— 危险!
  res.send(`<html><body>搜索: ${q}</body></html>`);
});

app.listen(3000);

访问 /search?q=<script>alert(1)</script> 会执行脚本(反射型)。

前端 DOM XSS(危险)

<div id="out"></div>
<script>
  const q = location.search.split('q=')[1] || '';
  document.getElementById('out').innerHTML = q; // 不转义 —— 危险(DOM XSS)
</script>

实战防范要点

  1. ​**输出编码(服务器端)**​:所有插入 HTML 的内容做 HTML 转义(&<>\"')。
  2. 前端最小化 innerHTML​:尽量用框架绑定(React/Vue 的模板)替代 innerHTML

    框架框出来的插值({value} / {{ value }})会​自动做 HTML 转义​,把 <>&"' 等关键字符替换成实体(&lt; 等),从而把攻击脚本当文本显示,而不是执行。

  3. 富文本白名单清洗​:对于必须存储/渲染的 HTML(富文本),后端用白名单 sanitizer(比如 bleach / html-sanitizer),前端再用 DOMPurify 做一次保护,对标签属性等进行清洗。
  4. Content-Security-Policy(CSP)头部​:禁止内联脚本、只允许可信源。
  5. HttpOnly Cookie 头部​:token/cookie 设置 HttpOnly,防止被脚本直接读取(减轻 XSS 后果)。

示例代码 — 安全改造

后端(Express + 转义)

const escapeHtml = s => String(s)
  .replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;')
  .replace(/"/g, '&quot;')
  .replace(/'/g, '&#39;');

app.get('/search', (req, res) => {
  const q = escapeHtml(req.query.q || '');
  res.send(`<html><body>搜索: ${q}</body></html>`);
});

前端(若必须渲染 HTML,用 DOMPurify)

<!-- npm install dompurify -->
<script src="https://unpkg.com/dompurify@2.<!--version-->/dist/purify.min.js"></script>
<div id="content"></div>
<script>
  // htmlFromServer 来自后端 API,仍需 sanitize
  const htmlFromServer = '<img src=x onerror=alert(1)>';
  document.getElementById('content').innerHTML = DOMPurify.sanitize(htmlFromServer);
</script>

设置 CSP(Nginx/Express header 示例)

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none';

二、CSRF(跨站请求伪造)

原理

利用用户已登录且浏览器会自动带上凭证(cookie)的特性,攻击者诱导用户发起对受信任站点的请求(如通过自动提交表单或图片请求),从而在用户 名下执行未授权操作。

最小复现示例(攻击者页面)

如果 bank.com/transfer 接受 GET 或 POST 并依赖 cookie 验证,攻击页面可这样写:

<!-- auto.html(在攻击者域名上) -->
<form action="https://bank.com/transfer" method="POST" id="f">
  <input name="to" value="attacker" />
  <input name="amount" value="1000" />
</form>
<script>document.getElementById('f').submit();</script>

用户在已登录 bank.com 的情况下访问攻击页面时,浏览器会自动带上 bank.com 的 cookie,导致转账。

防护要点

  1. SameSite Cookie​:把 session/cookie 设置 SameSite=LaxStrict(Lax 对 POST 有保护,适配大多数情形)。
  2. ​**CSRF Token(同步/双提交)**​:服务端生成随机 token,响应给前端;敏感请求必须携带并校验该 token。

    该 token 不同于 jwt token ,此处的 csrf-token 只为配合 session+cookie 传统鉴权策略做安全防护。

  3. 检查 Origin/Referer​:对跨站请求校验 OriginReferer 头(通常对 POST/PUT/DELETE 生效)。
  4. 避免用 cookie 做对外 API 的认证​:采用 Authorization: Bearer header 的 token 机制(只有 JS 能读/写),结合 CORS 限制。
  5. 敏感操作二次确认​:密码/OTP/二次验证。

示例代码(Express + scrf token + csurf)

csurf 使用 ​**双提交验证机制(CSRF Token)**​:

  1. 服务端生成一个 CSRF Token,放在 cookie 或 session 中。
  2. 前端每次发 POST/PUT/DELETE 请求要带上这个 token,常放在请求头或表单隐藏字段,比如:X-CSRF-Token: ey2423482374823748234
  3. 服务端校验 token,是否匹配、是否未过期、是否合法。

后端(Express)

// server.js
const express = require('express');
const cookieParser = require('cookie-parser');
const csurf = require('csurf');

const app = express();
app.use(cookieParser());
app.use(express.json());
app.use(csurf({ cookie: { httpOnly: true, sameSite: 'lax' } }));

app.get('/csrf-token', (req, res) => {
  // 返回 token 给 SPA 前端(用于后续请求 header)
  res.json({ csrfToken: req.csrfToken() });
});

app.post('/transfer', (req, res) => {
  // csurf 中间件会自动校验请求中的 token(_csrf 字段或 X-CSRF-Token header)
  // 执行转账逻辑...
  res.json({ ok: true });
});

app.listen(3000);

前端 SPA(获取 token 并在请求头中发送)

// 初始化时获取 token
async function init() {
  const r = await fetch('/csrf-token', { credentials: 'include' });
  const { csrfToken } = await r.json();
  window.__CSRF_TOKEN = csrfToken;
}

// 发送受保护请求
async function transfer() {
  await fetch('/transfer', {
    method: 'POST',
    credentials: 'include', // 仍然带 cookie
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': window.__CSRF_TOKEN
    },
    body: JSON.stringify({ to: 'bob', amount: 100 })
  });
}

只用 SameSite(简洁替代,适用多数场景),在服务端设置 cookie:

Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/;

这就能阻止绝大多数通过第三方页面触发的 POST/跨站敏感操作。

三、XSS 与 CSRF 的关键总结

概念:

  • XSS​:攻击者注入脚本并可读取页面内容(更强),根源是输出/DOM 不安全。
  • CSRF​:攻击者伪造用户请求,无法直接读取响应,根源是浏览器自动带凭证。

防护:

  1. 后端统一使用 HTML escape 库;富文本走白名单 sanitizer。
  2. 全站 Cookie:HttpOnly; Secure; SameSite=Lax
  3. 对需要的页面开启 CSP(report-only 先观测,再 enforce)。
  4. SPA:首次获取 CSRF token 并在后续请求中以 header 发送;服务端检查 Origin/Referer
  5. CI/代码审查禁止随意使用 innerHTML/eval/dangerouslySetInnerHTML
  6. 对关键操作实施二次验证(密码/OTP)。
### 前端 XSS 防御方法及 CSRF 预防策略面试题整理 #### XSS 防御方法 XSS(跨站脚本攻击)是一种常见的安全漏洞,攻击者可以通过注入恶意脚本窃取用户信息或操控用户行为。以下是防御 XSS 的常见方法: 1. **避免拼接 HTML** 在前端开发中,应尽量避免动态拼接 HTML 内容,改用框架提供的安全绑定机制。例如,在 Vue.js 中不要使用 `v-html`,在 React 中不要使用 `dangerouslySetInnerHTML`[^2]。 2. **转义输出内容** 对所有用户输入的内容进行严格的转义处理,确保特殊字符(如 `<`, `>`, `&` 等)被正确编码为 HTML 实体。可以使用成熟的库(如 `DOMPurify` 或 `he`)来实现这一功能。 3. **使用 Content-Security-Policy (CSP)** 通过设置 HTTP 响应头中的 `Content-Security-Policy`,限制页面可以加载的资源来源,从而防止恶意脚本执行。对于不支持 CSP 的旧版浏览器,可以考虑设置 `X-XSS-Protection` 头。 4. **过滤用户输入** 在后端对用户提交的数据进行严格校验过滤,确保只接收合法的输入格式。例如,可以使用正则表达式验证邮箱、手机号等字段[^1]。 5. **使用现代框架的安全特性** 现代前端框架(如 Vue.js React)默认会对模板中的数据进行自动转义,减少 XSS 攻击的风险。开发者只需遵循框架的最佳实践即可。 --- #### CSRF 预防策略 CSRF(跨站请求伪造)是一种利用用户身份认证状态发起恶意请求的攻击方式。以下是预防 CSRF 的常见方法: 1. **引入 CSRF Token** 在每个需要保护的表单或 API 请求中加入一个随机生成的 CSRF Token,并将其存储在用户的会话中。服务器在处理请求时验证该 Token 是否匹配。 2. **SameSite Cookie 属性** 设置 Cookie 的 `SameSite` 属性为 `Strict` 或 `Lax`,限制 Cookie 在跨站请求中的发送行为。这可以有效防止大多数 CSRF 攻击[^3]。 3. **验证 Referer Origin** 检查请求的 `Referer` 或 `Origin` 头是否来自可信域名。如果来源不可信,则拒绝处理该请求[^1]。 4. **双重提交 Cookie** 在请求中包含一个与 Cookie 值一致的参数,服务器验证二者是否匹配。这种方法简单易实现,但需注意安全性。 5. **限制 HTTP 方法** 对于敏感操作(如删除账户、修改密码),仅允许使用 `POST` 或其他非幂等性方法,避免通过 `GET` 请求直接触发操作。 --- #### 示例代码 以下是一个简单的 CSRF Token 实现示例: ```javascript // 前端:将 CSRF Token 添加到请求头中 function addCsrfTokenToHeaders() { const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); if (csrfToken) { fetch('/api/protected-endpoint', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ key: 'value' }) }); } } ``` 以下是一个使用 CSP 的 HTTP 响应头配置示例: ```http Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' 'unsafe-inline' ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秀秀不只会前端

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值