XSS (Cross-Site Scripting) 是一种能够将恶意脚本注入 Web 应用的攻击方式。
XSS 攻击的核心是破坏浏览器的同源策略(Same-Origin Policy)。攻击者通过欺骗目标网站执行恶意代码,使其在网站自身的上下文中运行,从而获得与网站代码相同的权限。
所有 XSS 攻击都依赖于网站做两件事:
- 接受可能由攻击者精心制作的输入
- 在页面中包含此输入而不进行清理(sanitizing)
- 发生在浏览器中,通过客户端渲染过程
- 恶意代码通过 DOM 操作(如
innerHTML)注入 - 常见于单页应用(SPA)
- 发生在服务器端,通过模板渲染过程
- 恶意代码在服务器构建页面时注入
- 常见于服务端渲染应用
- 恶意脚本存储在数据库中
- 每次用户访问页面时都会执行
- 危害最大,影响所有访问用户
将输入字符串中的危险字符进行转义,使其被视为文本而非可执行代码。
示例转换:
< → <
> → >
" → "
' → '
& → &
现代框架自动编码:
- Django 模板引擎自动执行输出编码
- React JSX 自动编码嵌入值
当需要插入 HTML 内容时,使用清理库移除不安全的特性。
推荐工具:
- DOMPurify - 被 OWASP 推荐的 HTML 清理库
示例:
// 危险输入
const input = '<img src=x onerror=alert("XSS")>Click me</img>';
// DOMPurify 清理后
const clean = DOMPurify.sanitize(input);
// 结果: 'Click me'确保输入在传递给危险 API 之前总是经过清理。
危险的 Web API:
element.innerHTMLdocument.write()eval()setTimeout()(字符串参数)
使用示例:
// 创建策略
const policy = trustedTypes.createPolicy('myPolicy', {
createHTML: (string) => DOMPurify.sanitize(string)
});
// 安全使用
element.innerHTML = policy.createHTML(userInput);作为备用防护,即使恶意脚本进入页面也能阻止其执行。
基于 Nonce 的 CSP:
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';基于哈希的 CSP:
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';- Nonce-based CSP: 适用于服务端渲染的页面
- Hash-based CSP: 适用于静态页面或单页应用
使用严格 CSP 时需要重构以下模式:
<!-- 危险 -->
<button onclick="doSomething()">Click</button>
<!-- 安全 -->
<button id="myButton">Click</button>
<script nonce="{NONCE}">
document.getElementById('myButton').addEventListener('click', doSomething);
</script><!-- 危险 -->
<a href="javascript:void(0)" onclick="doSomething()">Link</a>
<!-- 安全 -->
<a href="#" id="myLink">Link</a>
<script nonce="{NONCE}">
document.getElementById('myLink').addEventListener('click', function(e) {
e.preventDefault();
doSomething();
});
</script>// 危险
const obj = eval('(' + jsonString + ')');
// 安全
const obj = JSON.parse(jsonString);- 使用自动输出编码的模板引擎
- 了解不同文档上下文的编码需求
- 需要插入 HTML 时使用可信的清理库
- 实施严格的 CSP(基于 nonce 或哈希)
- 重构内联事件处理器和 JavaScript URI
- 避免使用
eval()和类似的代码执行函数 - 定期进行安全审计和代码审查
- 开发阶段: 使用 Lighthouse 检查 CSP 配置
- 测试阶段: 使用
Content-Security-Policy-Report-Only头进行测试 - 生产阶段: 部署完整的 CSP 策略
严格的 CSP 虽然提供强大保护,但在某些情况下仍可能被绕过:
- 通过可信脚本中的 DOM 操作漏洞
- 利用
'strict-dynamic'的特性 - 通过 JSONP 等机制
因此,CSP 应作为深度防护策略的一部分,而不是唯一的防护措施。