跨站脚本攻击(Cross-Site Scripting,XSS)是 Web 安全领域最古老、最普遍、也最容易被低估的漏洞类型之一。尽管 OWASP 多年来持续将其列入十大安全风险,XSS 依然在各类漏洞报告中高频出现。本文将从漏洞原理出发,系统梳理反射型、存储型和 DOM 型三种 XSS 的差异、挖掘方法与防御策略,帮助开发者和安全从业者建立完整的认知框架。
一、XSS 的本质
XSS 的核心在于:攻击者能够将恶意脚本注入到网页中,并使其在其他用户的浏览器中执行。由于浏览器无法区分页面中的脚本是来自开发者还是攻击者,一旦注入成功,恶意脚本就拥有了与合法脚本相同的权限——读取 Cookie、劫持会话、篡改页面内容、发起钓鱼攻击,甚至结合 CSRF 完成更复杂的攻击链。
XSS 之所以长期存在,根本原因在于用户输入与代码执行之间的边界被打破。当应用程序将不可信数据直接拼接到 HTML 输出中,而没有进行恰当的编码或过滤时,漏洞便产生了。
二、三种 XSS 类型的深入剖析
2.1 反射型 XSS(Reflected XSS)
反射型 XSS 是最常见的形式。攻击者将恶意脚本作为参数发送给服务器,服务器未经处理便将其“反射”回响应页面中,浏览器随即执行该脚本。
典型场景: 搜索功能、错误提示页、重定向参数。
例如,一个搜索页面通过 URL 接收关键词并直接显示:
https://example.com/search?q=<script>alert(document.cookie)</script>
如果服务器将 q 参数的值直接嵌入 HTML 响应中,脚本就会在受害者浏览器中执行。
挖掘思路:
- 关注所有回显用户输入的位置,包括 URL 参数、请求头(Referer、User-Agent)、表单字段。
- 测试时使用唯一标记字符串(如
xss123test),在响应中搜索该标记,确认回显点。 - 确认回显后,逐步测试特殊字符
<、>、"、'是否被编码或过滤,判断是否存在可利用的注入上下文(HTML 标签内、属性内、脚本块内等)。
反射型 XSS 通常需要诱导受害者点击恶意链接才能触发,因此常与钓鱼结合使用。
2.2 存储型 XSS(Stored XSS)
存储型 XSS 的危害最大。恶意脚本被永久存储在服务器端(数据库、文件系统、缓存等),当其他用户访问包含该数据的页面时,脚本自动执行,无需额外诱导。
典型场景: 评论区、用户昵称、论坛帖子、日志展示页面、客服工单系统。
一个典型的攻击流程是:攻击者在评论区提交包含恶意脚本的内容,服务器将其存入数据库。此后,任何访问该评论页面的用户都会触发脚本执行。如果管理员在后台查看评论,攻击甚至可以劫持管理员会话,导致整个系统沦陷。
挖掘思路:
- 枚举所有“输入后会被持久化并在其他页面展示”的功能点。
- 不仅关注前端展示,还要关注后台管理界面、邮件通知、导出功能等次级输出点。
- 测试时注意绕过长度限制和富文本过滤,常用 Payload 包括事件处理器(
onerror、onload)、svg标签、iframe等。
存储型 XSS 不需要受害者点击特制链接,因此常被用于蠕虫传播和批量账户劫持。
2.3 DOM 型 XSS(DOM-based XSS)
DOM 型 XSS 与前两者的关键区别在于:漏洞完全发生在客户端。服务器响应中不包含恶意脚本,脚本的注入和触发都由 JavaScript 在浏览器端完成。
典型场景: 前端路由、innerHTML 操作、eval() 调用、document.write()。
例如:
var name = location.hash.substring(1);
document.getElementById("greeting").innerHTML = "Hello, " + name;
攻击者构造 https://example.com/#<img src=x onerror=alert(1)>,脚本便在客户端被注入并执行。
挖掘思路:
- 审计前端 JavaScript 代码,追踪“源(Source)”到“汇(Sink)”的数据流。
- 常见 Source:
location、document.URL、document.referrer、window.name。 - 常见 Sink:
innerHTML、outerHTML、document.write、eval、setTimeout、location.href。 - 使用浏览器开发者工具或静态分析工具辅助定位可疑的数据流。
DOM 型 XSS 的隐蔽性较强,传统的服务端 WAF 往往难以检测,因为恶意载荷不会出现在 HTTP 请求中发送到服务器(如 # 后的内容不会被发送)。
三、XSS 的防御策略
防御 XSS 需要多层次、系统化的 approach,单一措施往往不足。
3.1 输出编码(Output Encoding)
这是最核心的防御手段。根据数据所处的上下文(HTML 正文、HTML 属性、JavaScript、CSS、URL),采用对应的编码方式:
- HTML 上下文:将
<、>、&、"、'编码为 HTML 实体。 - JavaScript 上下文:使用 Unicode 转义或 JSON 编码。
- URL 上下文:使用 URL 编码。
关键原则是:在输出时编码,而非在输入时过滤。因为同一份数据可能在不同上下文中被使用,输入时无法预知输出场景。
3.2 输入验证与白名单
对于预期格式明确的数据(如邮箱、电话号码、日期),采用严格的白名单验证。对于富文本内容,使用成熟的 HTML 净化库(如 DOMPurify)进行过滤,而非自己编写正则表达式。
3.3 Content Security Policy(CSP)
CSP 是一道强有力的纵深防御措施。通过设置 Content-Security-Policy 响应头,可以限制脚本的来源,禁止内联脚本执行:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'
即使攻击者成功注入了脚本,CSP 也能阻止其执行。建议逐步从 Content-Security-Policy-Report-Only 模式过渡到强制执行。
3.4 安全开发实践
- 使用现代前端框架(React、Vue、Angular)时,避免使用
dangerouslySetInnerHTML、v-html等绕过内置转义的 API。 - 设置 Cookie 的
HttpOnly属性,防止脚本读取会话 Cookie。 - 对用户可控数据进入 DOM 的操作,优先使用
textContent而非innerHTML。 - 在代码审查中重点关注 Source-to-Sink 的数据流。
3.5 自动化检测与持续监控
将 XSS 检测纳入 CI/CD 流程,使用 SAST(静态应用安全测试)和 DAST(动态应用安全测试)工具定期扫描。同时部署 WAF 作为辅助手段,但不能将其视为唯一防线。
四、结语
XSS 漏洞的三种类型各有特点:反射型依赖诱导点击,存储型危害最大且自动触发,DOM 型完全发生在客户端且难以被传统手段检测。理解它们的差异,有助于在挖掘时选择正确的测试策略,在防御时覆盖完整的攻击面。
安全的核心不在于堆砌防护措施,而在于理解数据的流动路径——从用户输入到最终输出的每一个环节,都是需要审视的风险点。只有在开发、测试、运维各阶段持续关注,才能真正将 XSS 的风险降到最低。
未经允许不得转载:任鹏个人博客 » XSS 漏洞全解析:反射型、存储型与 DOM 型跨站脚本攻击的挖掘与防御

