资讯详情

WAF编码绕过攻防实战:从解析差异到规则设计

📅 2026/10/15 11:41:39 | 华诺云谱 👁 阅读
WAF编码绕过攻防实战:从解析差异到规则设计
1. 从一个真实的绕过案例说起去年帮一个朋友看他们刚上线的业务日志里出现了一批很奇怪的请求。URL 里全是%25%32%37这种双重编码的字符乍一看像是乱码但把两层解码剥掉之后赫然是一个完整的注入语句。他们的 WAF 规则明明配了拦截为什么没触发原因很简单WAF 在检测时只做了一次 URL 解码而后端应用做了两次。这个时间差就是编码绕过最经典的利用点。Payload 编码绕过说白了就是攻击者把恶意内容用各种编码方式“化妆”让 WAF 的规则匹配不到但后端在真正处理请求时又会把它还原成可执行的内容。这中间涉及一个核心矛盾WAF 看到的字符串和后端应用最终解析出来的字符串往往不是同一个东西。谁对“最终形态”的理解更准确谁就赢。这篇文章我想把这件事讲透。不管你是刚接触 WAF 运维的新手还是已经配了几百条规则的老手我都会从编码绕过的底层逻辑讲起把常见手法、检测思路、规则写法、排查技巧一层层拆开。看完你至少能做到两件事第一知道攻击者会用哪些编码花招第二知道怎么让自己的 WAF 规则堵住这些花招而不是被一个%25就骗过去。2. 编码绕过的本质解析差异2.1 为什么同一个请求会有两种“真相”要理解编码绕过先得接受一个事实一个 HTTP 请求从客户端发出到后端业务逻辑处理中间要经过很多层解析。每一层都可能对字符串做一次解码或转换。WAF 站在某一层看后端站在另一层看两边看到的“真相”就可能不一样。举个最朴素的例子。攻击者想传union select但 WAF 规则里写了union\sselect就拦。于是他传union%20select。如果 WAF 在检测前会做一次 URL 解码那它看到的是union select能拦如果 WAF 不做解码它看到的是union%20select正则匹配不上就放过去了。而后端框架通常会自动做 URL 解码最终拿到的还是union select照样执行。这就是解析差异。WAF 的解码层数和后端的解码层数不一致或者解码的时机、范围不一致绕过就产生了。我常跟人说配 WAF 规则本质上是在做一件事让 WAF 的解析结果尽可能逼近后端的解析结果。逼近得越准绕过空间越小。2.2 编码绕过的三大类手法实际遇到的编码绕过基本可以归到三类里。第一类是多层编码。就是刚才说的双重、三重 URL 编码。%27是单引号%2527是%27再编码一次后端解两次就还原成单引号。层数越多WAF 越容易漏。第二类是异构编码。同一个字符有多种表示方式。比如单引号可以是%27URL 编码、%u0027某些场景下的 Unicode 编码、#39;HTML 实体、\x27十六进制转义。WAF 如果只认%27其他形式就漏了。第三类是编码与语法结合。这类最阴险因为它不只是编码问题还利用了后端语言的语法特性。比如在某些 SQL 方言里/**/可以当空格用union/**/select和union select等价但 WAF 如果只匹配空格就拦不住。再比如用代替空格、用大小写混写UnIoN、用注释截断union#select等等。注意这三类手法经常组合使用。一个成熟的攻击 payload 可能是“双重 URL 编码 注释代替空格 大小写混淆”的叠加体。所以 WAF 规则不能只防单一手法要考虑组合场景。2.3 一个关键认知解码不是越多越好新手容易走极端觉得“那我让 WAF 无限次解码不就行了”。这个思路有问题。无限解码会带来两个麻烦一是性能开销每个请求都反复解码QPS 高了扛不住二是误报正常业务里也可能出现%25这种字符你解到底可能把正常请求当成攻击。正确的做法是对齐后端的解码行为。后端解几次WAF 就解几次不多不少。这就要求你去摸清楚自己业务框架的解码逻辑。比如某些框架对 query string 解一次对 body 解一次对 path 又用另一套规则。这些细节决定了你的 WAF 该怎么配。3. 常见编码手法逐个拆解3.1 URL 编码与它的多层变体URL 编码是最基础的。规则很简单一个字节用%加两位十六进制表示。是%27空格是%20是%3C。多层编码就是在已经编码的字符串上再编码一次。%27里的%本身编码成%25于是%27变成%2527。再编一次变成%252527。后端如果解两次%2527就还原成%27再解一次才是。这里有个容易踩的坑不是所有后端都会解多层。有些框架只解一次那你传%2527它拿到的就是%27这个字面字符串不会变成单引号。所以攻击者要先探测目标解几次再决定编几层。作为防守方你要做的是确认自己后端解几次然后让 WAF 解同样的次数。我一般建议在 WAF 里对 URL 参数做最多两次解码然后对解码后的结果做规则匹配。为什么是两次因为绝大多数框架最多解两次解三次的情况极少而且解太多次误报率会上升。3.2 Unicode 编码的坑Unicode 编码有好几种写法%u0027是其中一种注意是%u不是%。这种编码在 IIS 某些版本上会被自动解析在别的服务器上可能不会。所以它是一把双刃剑用对了能绕过用错了后端不认payload 就废了。对 WAF 来说要不要处理%uXXXX取决于你的后端环境。如果后端是 IIS 或者某些会解析%u的框架那 WAF 必须把%u0027也解码成再匹配。如果后端根本不认这种编码那 WAF 处理它反而可能引入误报。我的经验是先确认后端认不认再决定 WAF 处不处理。不要盲目把所有编码形式都加进解码流程那样只会让规则越来越臃肿。3.3 HTML 实体编码在 XSS 场景的威力HTML 实体编码主要出现在 XSS 场景。可以写成lt;是gt;是quot;是#39;或#x27;。关键在于HTML 实体是在浏览器渲染时才解码的服务器端往往不解码。所以如果 WAF 只检查服务器端看到的原始字符串而攻击 payload 是lt;scriptgt;WAF 看到的是lt;scriptgt;匹配不到script就放过了。但这个内容如果被原样输出到页面浏览器会把它解码成script并执行。这就是存储型 XSS 里编码绕过的典型路径。防守思路是WAF 在检测 XSS 时除了看原始字符串还要对 HTML 实体做一次解码后再匹配。同时输出编码output encoding才是防 XSS 的根本WAF 只是补充。3.4 十六进制与八进制转义\x27是十六进制转义\047是八进制转义。这些主要出现在后端语言层面比如 PHP 的字符串里\x27就是单引号。攻击者如果在参数里传\x27某些场景下后端会把它当转义字符处理。这类编码的检测比较麻烦因为\x开头的字符串在正常业务里也可能出现比如某些 ID 生成规则。我的做法是对\x和\0开头的转义序列做解码后再匹配但同时给这类规则加一个白名单排除已知的正常业务模式。3.5 编码与语法混淆的组合拳单独一种编码往往不够真正难防的是组合。举几个我见过的真实组合union/**/select用注释代替空格UnIoN SeLeCt大小写混淆union%0aselect用换行符代替空格unionselect用加号代替空格/*!union*/ selectMySQL 特有的可执行注释这些手法的共同点是它们改变了 payload 的“字面形态”但没有改变它的“语义”。WAF 如果只做字面匹配就会被绕过。要防住规则必须做“归一化”——把注释去掉、把大小写统一、把各种空白符统一成空格然后再匹配。4. WAF 侧的解码与归一化设计4.1 解码流程该怎么设计一个合理的 WAF 解码流程我通常建议按这个顺序来先做 URL 解码最多两次再做 Unicode 解码如果后端支持再做 HTML 实体解码针对 XSS 检测最后做语法归一化去注释、统一空白、统一大小写这个顺序不能乱。为什么先 URL 后 Unicode因为 URL 编码是最外层的攻击者通常先做 URL 编码再做其他编码。如果你先解 Unicode可能把%u0027里的%当成 URL 编码的一部分解出错误结果。每一步解码后都要做一次规则匹配而不是全部解完再匹配。这样能覆盖“只编一层”和“编多层”两种情况。代价是性能开销增加所以实际部署时要根据业务 QPS 做取舍。4.2 归一化把“化妆”的 payload 打回原形归一化的核心思想是不管攻击者怎么改写 payload 的字面形式只要语义相同归一化后就应该得到相同的结果。具体做几件事去掉 SQL 注释/**/、--、#等统一空白符把%0a、%0d、%09、都统一成空格统一大小写把 payload 转成小写再匹配去掉多余括号和引号某些场景下((select))和select等价归一化之后再做正则匹配命中率会高很多。但归一化也有风险过度归一化可能把正常请求也“归一”成攻击特征导致误报。所以归一化规则要精细不能一刀切。4.3 规则写法的几个实战技巧写 WAF 规则时我有几个习惯。第一用“语义正则”而不是“字面正则”。比如匹配 SQL 注入不要写union\sselect而是写union[\s/**/]select把常见的分隔符都覆盖进去。第二对关键字符做“存在性检测”而不是“精确匹配”。比如检测单引号不要只匹配而是匹配[%27%2527\x27]这种集合。当然这要配合解码流程否则会误报。第三给规则加“上下文约束”。单独一个select可能是正常业务词但select后面跟着from再跟着where就高度可疑。用上下文约束能大幅降低误报。第四定期用“绕过测试集”回归。我维护了一个包含几百个编码变体的测试 payload 集合每次改规则都跑一遍看有没有漏网的。这个习惯帮我堵住了不少盲区。5. 实操搭建一套能扛编码绕过的检测流程5.1 环境与工具准备要验证自己的 WAF 能不能扛编码绕过你需要一个可控的测试环境。我一般这么搭一台测试服务器部署一个存在已知漏洞的靶场应用比如某个老版本的 CMS一个 WAF 实例可以是开源的也可以是商业的配置好规则一个 payload 生成脚本能自动生成各种编码变体一个日志分析工具用来对比 WAF 拦截记录和后端实际执行记录payload 生成脚本是关键。我写过一个 Python 小工具输入一个基础 payload它能自动生成 URL 编码、双重编码、Unicode 编码、HTML 实体编码、大小写混淆、注释插入等几十种变体。用这个工具批量打靶场就能快速发现 WAF 的盲区。import urllib.parse def generate_variants(payload): variants [] # 原始 variants.append(payload) # URL 编码 variants.append(urllib.parse.quote(payload)) # 双重 URL 编码 variants.append(urllib.parse.quote(urllib.parse.quote(payload))) # 大小写混淆 variants.append(.join(c.upper() if i % 2 else c.lower() for i, c in enumerate(payload))) # 空格替换 variants.append(payload.replace( , /**/)) variants.append(payload.replace( , )) variants.append(payload.replace( , %0a)) return variants base union select 1,2,3 for v in generate_variants(base): print(v)这个脚本很简单但覆盖了最常见的几类变体。实际用的时候可以再扩展加入 Unicode 编码、HTML 实体等。5.2 关键参数的计算与选择WAF 解码层数设几次这个参数不是拍脑袋定的。我的计算方法是先看后端框架的解码逻辑。查文档或者直接抓包看后端收到的原始字符串。统计正常业务请求里%字符的出现频率。如果频率很低说明多层解码的误报风险小可以多解一层。压测 WAF 在不同解码层数下的性能。每多解一层CPU 开销大概增加 5% 到 15%具体看实现。综合下来我一般设两次 URL 解码。这个值在“覆盖大多数攻击”和“控制误报与性能”之间比较平衡。如果你的业务对安全要求极高可以设三次但要接受更高的误报率和性能开销。5.3 完整检测流程的落地把上面的东西串起来一个完整的检测流程是这样的请求进入 WAF提取 URL、Header、Body 中的参数对每个参数做第一层 URL 解码匹配规则对第一层解码结果做第二层 URL 解码匹配规则对第二层解码结果做 Unicode 解码匹配规则对结果做 HTML 实体解码匹配 XSS 规则对结果做语法归一化匹配 SQL 注入规则任何一步命中就拦截并记录这个流程听起来繁琐但实际实现时可以用“管道”模式把各个解码器和匹配器串起来代码结构很清晰。性能上大部分请求在第一层或第二层就被放行了只有可疑请求才会走完全流程。提示记录日志时一定要记录“原始 payload”和“解码后 payload”两个版本。排查问题时这两个版本能帮你快速定位是解码环节出了问题还是规则本身有问题。6. 常见问题与排查技巧实录6.1 为什么规则明明配了还是被绕过这是被问得最多的问题。我总结了几种常见原因做成速查表现象可能原因排查方法规则匹配不到WAF 解码层数少于后端抓包对比 WAF 和后端看到的字符串规则匹配不到攻击用了异构编码检查 payload 里有没有%u、#、\x规则匹配不到攻击用了语法混淆检查有没有注释、大小写、空白符替换规则误报归一化过度检查归一化规则是否把正常字符也改了规则误报上下文约束太松给规则加前后文限定性能下降解码层数太多压测不同层数下的 QPS排查时我有个习惯先把 WAF 的检测日志和后端的访问日志对齐时间戳找到同一个请求对比两边看到的 payload。这个对比能直接暴露解析差异在哪里。6.2 误报和漏报的平衡怎么找误报和漏报是一对矛盾。解码层数多、归一化激进漏报少但误报多反过来则误报少但漏报多。我的做法是分级处理。把规则分成三档高危规则命中直接拦截比如明确的 SQL 注入特征中危规则命中记录并告警但不拦截比如可疑的编码模式低危规则只记录用于后续分析这样既能拦住大部分攻击又不会因为误报影响正常业务。中危和低危规则的日志定期分析如果发现某条规则长期只产生误报就调整或下线如果发现某条规则漏掉了真实攻击就升级为高危。6.3 几个我踩过的坑第一个坑以为所有后端都解两次 URL 编码。有次给一个业务配 WAF按两次解码配的结果那个业务的后端只解一次。攻击者传%2527WAF 解两次得到拦截了但后端只解一次得到%27根本不执行。这导致大量正常请求里带%25的被误拦。后来改成按后端实际行为配问题才解决。第二个坑忽略了 Header 里的编码。很多人只检查 URL 和 Body忘了 Cookie、Referer、User-Agent 这些 Header 也可能藏 payload。有次攻击就是把注入语句放在 Cookie 里用双重编码绕过因为 WAF 没检查 Cookie 的解码。第三个坑归一化把正常业务词改坏了。有次归一化规则里把--当注释去掉结果业务里有个参数值正好是A--B这种格式被改成了AB导致规则误判。后来给归一化加了白名单排除已知的正常模式。第四个坑性能没做压测。上线前没压测结果高峰期 WAF 的 CPU 打满请求延迟飙升。后来把解码层数从三次降到两次又加了缓存才稳住。这件事让我养成了“任何规则改动都要压测”的习惯。6.4 持续对抗的思路编码绕过不是一次配好就完事的。攻击手法在进化新的编码方式、新的语法特性会不断出现。所以 WAF 规则需要持续维护。我的做法是每周看一次 WAF 拦截日志找有没有“疑似绕过”的请求比如解码后出现敏感词但没被拦的每月更新一次 payload 测试集加入新发现的变体每季度做一次完整的绕过测试用自动化工具批量打关注安全社区的新披露看到新的绕过手法就第一时间验证自己的 WAF 能不能防这套流程坚持下来WAF 的防护效果会越来越稳。反过来如果配完就不管了半年后基本就是个摆设。7. 一些个人体会做 WAF 这几年我最大的感受是编码绕过本质上是“解析权”的争夺。谁对请求的最终形态理解得更准确谁就掌握主动。WAF 要做的不是“比攻击者聪明”而是“比后端更懂后端”。你得清楚后端每一步怎么解析、怎么解码、怎么执行然后让 WAF 的检测逻辑跟它对齐。另一个体会是没有万能的规则只有持续的对抗。任何一条规则都有被绕过的可能关键是建立一套发现绕过、分析绕过、修补规则的闭环。工具和规则是死的人是活的。最后分享一个小技巧如果你不确定某个 payload 会不会绕过你的 WAF最直接的办法是把它同时发给 WAF 和后端对比两边的处理结果。WAF 放过了但后端执行了就是绕过WAF 拦了但后端本来也不会执行就是误报。这个对比测试做多了你对编码绕过的直觉会越来越准。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑