是什么
XSS(Cross-Site Scripting,跨站脚本):攻击者把恶意 JavaScript 注入到网页中,当其他用户浏览该页面时,脚本在其浏览器中执行。
危害:
- 窃取 Cookie / 会话令牌(进而冒充用户);
- 读取页面上的敏感信息;
- 以用户的身份发起请求(转账、发帖、改密码);
- 重定向到钓鱼网站;
- 植入挖矿脚本或键盘记录器。
三种类型
一、反射型 XSS(Reflected)
恶意脚本作为请求参数的一部分,被服务器"反射"回页面中。
https://example.com/search?q=<script>alert(1)</script>如果服务器把 q 的值直接插入 HTML 响应,脚本就会执行。
特点:需要诱导用户点击特制的链接(通常通过钓鱼邮件、社交工程)。
二、存储型 XSS(Stored)
恶意脚本被保存在服务器上(如评论区、用户资料、论坛帖子),之后每次有用户访问该页面都会执行。
危害最大——因为不需要诱导点击特定链接,访问正常页面就会中招。
经典案例:早期社交网站的"蠕虫"——用户在个人资料中植入脚本,访问其主页的人被感染,并以他们的名义继续传播。
三、DOM 型 XSS
漏洞存在于客户端 JS 代码中——脚本直接从 location.hash、document.referrer、或 DOM 中读取数据,未经处理就写入页面。
// 危险
document.getElementById('msg').innerHTML = location.hash;特点:恶意数据可能根本不经过服务器(如在 URL 的 # 片段中),因此服务端过滤对它无效。
防御
一、输出编码(最核心)
根据输出上下文,对数据进行相应的编码。
这是最重要的原则——XSS 的本质是"数据被当成代码执行",编码就是让数据"保持为数据"。
| 上下文 | 需要转义 |
|---|---|
| HTML 元素内容 | < > & " ' |
| HTML 属性 | 同上,且属性值要加引号 |
| JavaScript 中 | 按 JS 字符串转义,或用 JSON.stringify |
| URL 参数 | URL 编码 |
| CSS | CSS 转义 |
使用现代框架:React、Vue、Angular 等默认对插值内容进行 HTML 转义——这自动防御了大部分 XSS。
但要注意例外:
- React 的
dangerouslySetInnerHTML; - Vue 的
v-html; - Angular 的
bypassSecurityTrustHtml。
这些"逃生舱口"会绕过转义,使用时必须确保内容可信或已净化。
二、内容安全策略(CSP)
通过 HTTP 响应头告诉浏览器:只允许加载和执行来自特定来源的脚本。
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com关键特性:
- 禁止内联脚本(
<script>...</script>和onclick=等内联事件处理器)——这是 XSS 的主要载体; - 禁止
eval(); - 限制脚本来源白名单。
如果必须允许内联脚本,可以用 nonce(每次响应生成一个随机值)或 hash 来精确授权。
CSP 是很强的纵深防御——即便存在 XSS 漏洞,也能阻止恶意脚本执行或外传数据。
三、HttpOnly Cookie
Set-Cookie: session=xxx; HttpOnly; Secure; SameSite=LaxHttpOnly 使 JS 无法读取该 Cookie——即使发生 XSS,攻击者也无法窃取会话令牌。
注意:这不能阻止 XSS 的其他危害(如以用户身份发请求),但能防止最直接的凭据窃取。
四、输入验证与净化
对 HTML 内容(如富文本编辑器),用专门的净化库(如 DOMPurify、sanitize-html)移除危险的标签和属性。
不要自己写正则去"过滤"——HTML 的绕过方式太多,自己写的过滤器几乎必然有漏洞。
注意:输入验证是补充,不能替代输出编码——因为同一个数据可能输出到多个不同的上下文。
五、避免危险 API
- 避免
innerHTML、outerHTML、document.write(); - 需要插入文本时用
textContent; - 避免
eval()、new Function()、setTimeout('string')。
一个现实观察
现代前端框架的普及,大幅降低了 XSS 的发生率——因为默认转义。
XSS 现在更多出现在:
- 使用了
dangerouslySetInnerHTML/v-html的地方; - 服务端渲染的模板中漏掉转义;
- 富文本内容的展示;
- 第三方脚本的引入(这属于供应链风险)。
一句话
永远不要把不可信数据当作代码执行。 在输出时按上下文编码,是第一道也是最有效的防线。
说明:本文为 Web 安全的知识整理。