XSS / CSRF 漏洞修复实战
场景
安全团队报告项目存在 XSS 和 CSRF 漏洞,需要紧急修复。
XSS 修复
1. 三种 XSS 类型与修复
| 类型 | 触发方式 | 修复方案 |
|---|---|---|
| 存储型 XSS | 恶意内容存入数据库后渲染 | 输出编码 + CSP |
| 反射型 XSS | URL 参数直接插入页面 | 输入过滤 + 输出编码 |
| DOM 型 XSS | JS 直接操作 DOM 插入用户数据 | 避免 innerHTML |
2. 修复代码
xss-prevention.ts
// ❌ 危险操作
element.innerHTML = userInput;
document.write(userInput);
// ✅ 安全操作
element.textContent = userInput;
// ✅ 需要渲染 HTML 时,使用白名单过滤(DOMPurify)
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target'],
});
csp-header.ts
// CSP 策略(后端设置 HTTP Header)
// Content-Security-Policy: default-src 'self';
// script-src 'self' 'nonce-abc123';
// style-src 'self' 'unsafe-inline';
// img-src 'self' data: https:;
// React 中防止 XSS
// React 默认对 JSX 内容进行转义
// ❌ 唯一的风险:dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }} />
3. URL 参数防护
url-param-safe.ts
// ❌ 直接使用 URL 参数
const query = new URLSearchParams(location.search).get('q');
document.getElementById('search')!.innerHTML = `搜索:${query}`;
// ✅ 使用 textContent
document.getElementById('search')!.textContent = `搜索:${query}`;
// ✅ URL 中 javascript: 协议防护
function isSafeUrl(url: string): boolean {
try {
const parsed = new URL(url, location.origin);
return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
} catch {
return false;
}
}
CSRF 修复
1. CSRF Token
csrf-protection.ts
// 后端生成 CSRF Token 放在 cookie(httpOnly: false)
// 前端从 cookie 读取并放入请求头
function getCsrfToken(): string {
const match = document.cookie.match(/csrf_token=([^;]+)/);
return match?.[1] ?? '';
}
// Axios 全局配置
import axios from 'axios';
axios.defaults.headers.common['X-CSRF-Token'] = getCsrfToken();
2. SameSite Cookie
// 后端 Set-Cookie 配置
Set-Cookie: session=xxx; SameSite=Strict; Secure; HttpOnly
| SameSite 值 | 效果 |
|---|---|
| Strict | 完全禁止第三方 Cookie |
| Lax | 允许顶层导航的 GET 请求携带 |
| None | 允许跨站(需配合 Secure) |
常见面试问题
Q1: React/Vue 能完全防 XSS 吗?
答案:
基本能,但有例外:
- React 默认对 JSX 表达式转义(
{userInput}是安全的) - Vue 的
{{ }}模板语法默认转义 - 例外:
dangerouslySetInnerHTML(React)/v-html(Vue)会绕过转义,需要手动用 DOMPurify 过滤
Q2: CSRF Token 和 SameSite Cookie 哪个更好?
答案:
推荐同时使用:
- SameSite=Lax:简单有效,主流浏览器已默认支持
- CSRF Token:防御更严密(应对 SameSite 不支持的旧浏览器)
- 双重校验:Token + 自定义 Header(Ajax 请求自动带,表单提交不带,天然区分)