您现在的位置是:网站首页 > 防御 XSS 的最佳实践文章详情
防御 XSS 的最佳实践
陈川
【
前端安全
】
48152人已围观
7106字
理解 XSS 攻击的本质
XSS(跨站脚本攻击)的核心在于攻击者通过注入恶意脚本到受信任的网站上,当其他用户访问该网站时,这些脚本会在用户的浏览器中执行。XSS 攻击主要分为三类:存储型、反射型和 DOM 型。存储型 XSS 将恶意脚本永久存储在目标服务器上,反射型 XSS 通过 URL 参数即时反射恶意脚本,而 DOM 型 XSS 则完全在客户端执行,不经过服务器。
// 一个典型的反射型 XSS 示例
// 攻击者构造的恶意 URL:http://example.com/search?query=<script>alert('XSS')</script>
const query = new URLSearchParams(window.location.search).get('query');
document.getElementById('results').innerHTML = query; // 直接插入未转义的用户输入
输入验证与过滤
对所有用户输入进行严格的验证是防御 XSS 的第一道防线。前端和后端都应该实施验证规则,确保输入符合预期格式。使用白名单机制比黑名单更安全,因为黑名单很难覆盖所有可能的攻击向量。
// 使用正则表达式验证邮箱格式
function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(String(email).toLowerCase());
}
// 过滤 HTML 标签的简单实现
function stripTags(str) {
return str.replace(/<[^>]*>/g, '');
}
输出编码
在将用户提供的内容插入到 HTML 中时,必须进行适当的编码。不同的上下文需要不同的编码方式:
- HTML 内容编码:将特殊字符转换为 HTML 实体
- HTML 属性编码:对属性值进行编码
- JavaScript 编码:当动态生成 JavaScript 代码时
- URL 编码:用于 URL 参数
// HTML 实体编码函数
function htmlEncode(str) {
return str.replace(/[&<>'"]/g,
tag => ({
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
}[tag]));
}
// 使用示例
document.getElementById('user-comment').textContent = userInput; // 安全方式
// 而不是:
document.getElementById('user-comment').innerHTML = userInput; // 危险方式
使用现代前端框架的安全特性
现代前端框架如 React、Vue 和 Angular 都内置了 XSS 防护机制:
- React:默认对所有渲染内容进行转义,除非使用
dangerouslySetInnerHTML
- Vue:
v-text
指令自动转义内容,v-html
需要显式使用 - Angular:插值语法
{{}}
自动转义,[innerHTML]
需要谨慎使用
// React 中的安全实践
function SafeComponent({ userInput }) {
return (
<div>
{/* 安全:自动转义 */}
<div>{userInput}</div>
{/* 危险:需要确保 userInput 是可信的 */}
<div dangerouslySetInnerHTML={{ __html: userInput }} />
</div>
);
}
内容安全策略 (CSP)
CSP 是一个强大的防御层,通过 HTTP 头告诉浏览器哪些资源可以加载和执行。一个严格的 CSP 可以阻止大多数 XSS 攻击:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:
关键指令包括:
default-src
:默认加载策略script-src
:控制 JavaScript 加载style-src
:控制 CSS 加载img-src
:控制图片加载connect-src
:控制 AJAX、WebSocket 等连接
安全的 Cookie 实践
防止通过 XSS 窃取 Cookie:
- 设置
HttpOnly
标志,防止 JavaScript 访问 - 设置
Secure
标志,仅通过 HTTPS 传输 - 考虑使用
SameSite
属性防止 CSRF
// Express 中设置安全 Cookie 的示例
app.get('/login', (req, res) => {
res.cookie('sessionID', '12345', {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000
});
});
DOM 型 XSS 的防御
DOM 型 XSS 特别危险,因为它完全在客户端发生,不经过服务器。防御措施包括:
- 避免使用
innerHTML
,改用textContent
- 谨慎处理
location.hash
、document.referrer
等来源 - 使用安全的 API 如
DOMPurify
清理 HTML
// 使用 DOMPurify 清理 HTML
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHtml);
document.getElementById('content').innerHTML = clean;
安全的第三方库使用
第三方库可能成为 XSS 的入口:
- 定期更新依赖库
- 使用受信任的来源
- 审查库的源代码,特别是处理用户输入的部分
- 使用子资源完整性 (SRI) 验证 CDN 资源
<!-- 使用 SRI 的 script 标签示例 -->
<script src="https://example.com/script.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
测试与自动化检测
建立自动化的安全测试流程:
- 使用工具如 OWASP ZAP、Burp Suite 进行扫描
- 实施单元测试检查 XSS 漏洞
- 进行代码审查,重点关注用户输入处理
- 使用 linter 插件检测潜在危险模式
// 简单的 XSS 检测单元测试示例
test('should escape HTML in user input', () => {
const maliciousInput = '<script>alert("XSS")</script>';
const safeOutput = escapeHtml(maliciousInput);
expect(safeOutput).not.toContain('<script>');
expect(safeOutput).toContain('<script>');
});
安全的富文本编辑处理
处理富文本内容时需要特别小心,因为需要保留一些 HTML 标签但过滤危险的属性和标签:
- 使用专门的库如
DOMPurify
或sanitize-html
- 定义允许的标签和属性白名单
- 过滤
on*
事件处理程序和javascript:
协议
// 使用 sanitize-html 的配置示例
const sanitizeHtml = require('sanitize-html');
const clean = sanitizeHtml(dirtyHtml, {
allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
allowedAttributes: {
'a': ['href', 'title']
},
allowedSchemes: ['http', 'https']
});
安全的 JSON 数据处理
当动态生成 JavaScript 或 JSON 时,确保正确处理数据:
- 使用
JSON.stringify()
将数据转换为字符串 - 在 HTML 中嵌入 JSON 时,先进行 HTML 转义
- 避免拼接字符串生成 JavaScript 代码
// 安全的 JSON 嵌入方式
const userData = {
name: "John \"<script>alert(1)</script>",
age: 30
};
// 正确做法
const safeJson = JSON.stringify(userData)
.replace(/</g, '\\u003c'); // 额外转义 < 字符
document.getElementById('data').textContent = safeJson;
// 错误做法:直接拼接
const unsafeScript = `<script>var data = ${JSON.stringify(userData)};</script>`;
HTTP 安全头部的综合应用
除了 CSP,其他安全头部也能增强 XSS 防护:
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer-when-downgrade
Feature-Policy: geolocation 'none'; microphone 'none'
这些头部可以:
- 启用浏览器内置的 XSS 过滤器
- 防止 MIME 类型嗅探攻击
- 阻止点击劫持
- 控制 referrer 信息泄露
- 限制敏感 API 的使用
安全的动态代码执行
避免使用 eval()
、new Function()
和 setTimeout/setInterval
的字符串参数形式,这些都可能成为 XSS 的入口点:
// 危险的做法
const userInput = 'alert("XSS")';
eval(userInput);
// 安全的替代方案
const safeFunction = () => console.log('Safe');
setTimeout(safeFunction, 100);
// 如果必须动态执行代码,确保完全控制输入源
const trustedInput = validateAndSanitize(userInput);
const func = new Function('param', `console.log(${JSON.stringify(trustedInput)})`);
安全的 URL 处理
处理 URL 时需要特别注意:
- 验证 URL 的协议(禁止
javascript:
) - 使用
URL
API 而不是字符串拼接 - 对动态生成的 URL 进行编码
// 安全的 URL 验证
function isSafeUrl(url) {
try {
const parsed = new URL(url, window.location.href);
return ['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol);
} catch {
return false;
}
}
// 使用示例
const userLink = getUserInput();
if (isSafeUrl(userLink)) {
document.getElementById('link').href = userLink;
} else {
// 处理不安全 URL
}
前端模板的安全使用
使用模板引擎时要注意:
- 选择自动转义的模板引擎(如 Handlebars、EJS)
- 了解模板的转义行为
- 避免拼接未转义的 HTML
// Handlebars 安全示例
const source = "<p>Hello, {{name}}!</p>";
const template = Handlebars.compile(source);
const safeHtml = template({ name: userInput }); // 自动转义
// 如果需要不转义,必须显式声明
const unsafeTemplate = Handlebars.compile("<p>Hello, {{{rawName}}}!</p>");
安全的 WebSocket 和实时通信
实时通信也可能引入 XSS:
- 验证服务器推送的消息
- 在客户端渲染前对消息内容进行编码
- 使用结构化消息而非原始 HTML
// WebSocket 消息处理示例
socket.onmessage = function(event) {
const message = JSON.parse(event.data);
const content = document.createElement('div');
content.textContent = message.text; // 安全渲染
document.getElementById('chat').appendChild(content);
// 而不是:
// document.getElementById('chat').innerHTML += message.html; // 危险
};
移动端和混合应用的特殊考虑
移动混合应用(如 Cordova、Capacitor)需要注意:
- 禁用
allow-navigation
到不可信源 - 谨慎使用
InAppBrowser
和 WebView - 验证从原生层传递到 WebView 的数据
<!-- Cordova config.xml 安全配置示例 -->
<allow-navigation href="https://example.com/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
持续的安全教育与意识培养
技术措施之外,团队的安全意识同样重要:
- 定期进行安全培训
- 建立安全编码规范
- 鼓励报告潜在漏洞的文化
- 保持对新型 XSS 攻击手法的了解
上一篇: 常见的 XSS 攻击示例