ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WAF规则层注入绕过全解析:原理、手法与加固

WAF规则层注入绕过全解析:原理、手法与加固 WAF这东西圈里人对它的态度一直很分裂。有人把它当万能保险箱规则一开就高枕无忧有人觉得它就是个摆设随便一个变形的注入就能打穿。我做了几年安全和运维相关的工作大大小小的WAF也见了不少说实话这两种看法都不准确。WAF绕过尤其是我今天要聊的规则层面注入绕过本质上是两套语义理解系统之间的博弈——规则引擎试图通过模式匹配来识别坏人而攻击者要做的只是改变一下表达方式让同样的恶意逻辑穿上不同的衣服。这篇文章我不想讲那种上来就甩几个payload让你背下来的教程而是想把从入门到理解整个绕过逻辑的过程拆开来讲把为什么能绕过这个底层原因讲透同时给出对应的规则加固建议。适合刚入门的安全学习者、运维工程师以及所有想搞明白我的WAF到底拦得住什么、拦不住什么的人。1. WAF 到底在拦什么先搞懂规则引擎的工作逻辑1.1 一次请求进来之后WAF做了哪些事你在前面加了一层WAF之后用户发来的每一个HTTP请求都得先过它这一关。整个过程大致可以拆成四步解析请求把HTTP方法、URL路径、Header、Cookie、Body参数等结构化拆开。这一步各家做得深浅不一但基本都要做。归一化把解码后的内容统一成一种标准形态。比如把URL编码还原、把重复的斜杠去掉、把大小写做归一化处理。规则匹配拿归一化后的数据去跟规则库里的特征正则进行比对。只要有一处命中就可以判定为攻击。执行动作拦截、记录日志、返回403或者只告警不拦截很多绕过测试其实是在这一步钻了空子。你可以把WAF想象成一个负责查证件的门卫。这个门卫手里有一张写满了可疑特征的黑名单任何人的证件上只要出现了名单里的字眼就被拦在外面。问题来了——这个门卫只看字面不读语义。它并不理解你证件上那行字实际在说什么它只知道这几个字连在一起等于危险。这就是规则层面检测的根本逻辑**它假设恶意行为一定会以某种固定模式出现。**但现实世界里的SQL注入语句并不是只有一种写法。你写成union select是注入写成UnIoN/**/SeLeCt还是注入写成union all select照样是注入。门卫手里那张黑名单再长也不可能穷举完同一个语义的所有字面形态。1.2 一条注入检测规则到底长什么样下面这条正则代表了绝大多数WAF检测SQL注入的典型思路union[\s\S]*?select \s*(or|and)\s*[\w\s]\s*[\w] information_schema[\s\S]*?table倒也不难理解就是找union后面跟着select这种组合找引号后面跟着or/and再跟个条件判断找查表名时必备的information_schema。这些特征确实覆盖了大量最常见的注入场景。但反过来想如果你的业务代码里压根没有能把用户输入拼进SQL语句的地方这些规则再强也就是摆设。反过来说一个精心构造的注入请求只要用法和规则库里枚举的特征长得不一样引擎就认不出来。规则的覆盖范围是有限的而攻击者的表达可能性是无限的这个不等式是规则层面绕过永远存在的根本原因也是我今天想重点展开的核心矛盾。2. 规则为什么会失效检测缺陷的底层原因2.1 黑名单思维的天花板同一种语义有无数种写法先做个简单的思维实验。假设你的规则库里有一条等号检测规则看到参数里出现就触发告警目的是拦住 or 11--这种万能密码。那我问你下边这些表达式在SQL里是不是同样成立 or 1 like 1-- or 2-11-- or aa-- or 1 in (1)-- or 1 between 0 and 2--全都是。11可以写作1 like 1可以写作2-11可以写作aa甚至可以写作1 between 0 and 2。你要是把每一种等价写法都加到规则库里规则库会无限膨胀而SQL语言本身是图灵完备的你可以一直构造新的等价表达。试图用穷举法去堵一个无限空间的攻击面从数学上就已经输了。真实里遇到过一个有意思的案例。某个系统的WAF拦了or 11测试的人随手把它改成or aa规则就放行了。原因很简单规则库里只枚举了数字型的11没有枚举字符串型的aa。你说这条规则没用吗它能拦90%的脚本小子的自动扫描。但你说它可靠吗只要是个稍微懂点SQL变形的人就能绕过。规则的作用是提高攻击成本而不是杜绝攻击这个概念一定要摆在前面。2.2 编码与归一化的不对称性看到的不等于执行的更高级一点的绕过思路是利用解析顺序的差异。举个很经典的例子。一个请求到达WAF之后WAF先做了一次URL解码然后拿解码后的结果去匹配规则——如果规则没有命中WAF就把这个请求转发给后端Web服务器。Web服务器接收到请求之后也要做URL解码然后把解码后的参数送进业务逻辑。到这里两边做的似乎是一样的没毛病。但假如这个中间层不止一个呢假如反向代理先解了一次WAF再解一次到达后端时又解了一次呢每一次解码都会多还原一层编码。假如攻击者把payload做双重URL编码——%2527先被WAF解成%27此时WAF看到的还是百分号开头的形态规则库里如果只写了匹配单引号的规则那它匹配不到。请求继续往下走后端拿到%27再解一次变成真的单引号注入就发生在这一层。这种我看到的和你看到的不一样的问题专业上叫归一化不一致。WAF以为自己在检查用户真实输入实际上它检查的只是被WAF解码过一次的输入而后端最终执行参数时用的是被所有中间层解码后的总和。这中间差出来的一层解码就是绕过可以利用的空间。不只是URL编码Unicode编码、HTML实体编码、十六进制编码原理都一样。对付这种绕过的核心思路是把解码流程规范化所有请求只解一次、按同一顺序解并且确保WAF看到的字符串和后端执行时看到的完全一致。2.3 你拦的是结果不是过程还有一类绕过的思路不是改变字符串长相而是改变攻击位置。很多注入规则只盯着参数值做检查。比如?id1 and 11这个注入点藏在参数值里。但有些系统会把参数名本身也拼进查询语句里比如一些老旧的排序功能?orderid被拼成ORDER BY {order}如果把order的值改成if(11,id,name)注入就发生在参数名这个位置。不少规则只扫值不扫名于是放行。还有Header里的注入、Cookie里的注入、Content-Type里的注入这些位置的安全检测强度往往远低于参数体。不是没有规则覆盖而是规则库的权重设计偏向于最常见的攻击位置非主流位置就容易被忽略。**你拦的是已经有规则覆盖的那些位置和形态而不是攻击者真正发起攻击的位置和形态。**这也是规则层面绕过的另一种共性规则库覆盖不全不等于攻击者找不到可以利用的位置。3. 规则层面注入绕过的常见手法拆解下面这部分我会按手法类别来拆。每一项都不仅仅给例子而是把背后的原理和对应的加固思路都讲清楚。再次强调一下所有测试都必须在授权的靶场环境或自己有权限的系统上进行。3.1 大小写、空白与注释符的排列组合这是最简单也最容易被忽略的一类。很多规则库里的正则默认区分大小写对空白符的定义又不够宽泛于是union select -- UnIoN SeLeCt union select -- union/**/select union select -- union%0bselect union select -- union select第一眼看上去像是同一个东西但在正则引擎看来是几种不同的字符串。如果规则写的是union\sselect那union/**/select就完全不在匹配视野里如果规则写的是union select中间一个空格那union select两个空格也能溜过去。这块儿的加固思路非常直接所有检测特征统一走忽略大小写 剥离注释 压缩空白的预处理把union/**/select先还原成union select再去匹配。我们实测下来这一步能拦掉大量低水平变形。很多厂商的基础规则其实已经做了但自建规则的人最容易踩这个坑——自己加规则的时候贪图省事直接抄了一个区分大小写的正则就往上挂。3.2 等价函数与运算符替代绕的不是规则是关键词注入特征并不只有union select这一种。很多WAF的注入规则会重点盯几个高危关键词information_schema查表结构必备concat/group_concat拼接数据substr/mid截取字符//条件判断sleep/benchmark时间盲注攻击者的思路就四个字换一个能实现同样效果的词。规则盯防的关键词绕过替代方案效果说明information_schema.tablesmysql.innodb_table_stats新版MySQL中可以直接从存储引擎统计表里拿表名不需要碰information_schemasubstr()mid()/left()/right()取子串的函数有多个等价选择规则只拦了一个like/regexp/in/between条件判断的等价写法非常多concat()直接并列字符串a b部分数据库支持隐式拼接sleep(5)benchmark(10000000,sha(1))时间盲注的函数不止sleep一个这类绕过的段位已经比大小写变形高一些了因为攻击者对SQL语法体系的熟悉程度决定了它能变出多少种花样。规则加固的思路却不能是继续加关键词——你加了mid人家还能用right你把常见时间盲注函数都加了人家还能写自定义函数。**更可靠的做法是不让用户输入进入SQL语法语义层面参数化查询/预编译而不是在关键词层面做军备竞赛。**WAF规则层只是兜底不是主线。3.3 编码变形与双重解码为什么总能骗过规则前面2.2节讲过编码不对称的原理这里给几个具体的例子# 单引号的URL编码 -- %27 # 双重URL编码 -- %2527 # Unicode编码部分中间件支持 -- %u0027 # 十六进制编码用于后端拼接 id-1 union select 1,2,3 id-1 union select 0x31,0x32,0x33先说一个实际场景。某次测试里一个请求的User-Agent头里有 or 11--加了单次URL编码%27%20or%2011--之后美图秀秀级别的基础WAF直接拦下来了——说明规则里有URL解码环节解码后的内容被识别了。但改成双重编码%2527%2520or%252011--就放行了。后端如果正好对这个值做两次解码这个引号就能被还原出来。虽然这个场景最后没形成实际注入因为参数没进SQL语句但检测层面确实漏了。加固这块的思路分两步。第一步在WAF的预处理环节统一做标准化解码操作连续解码直到没有可解码的字符再统一进行大小写归一化。第二步在Web服务器层面控制解码次数不要让一个参数经过两个中间件各解一次。两层一起做才能从根上消除看到的和执行的不是一个值的问题。3.4 参数解析差异与协议层绕过这一类的底层原因不是规则库写得不好而是解析器的解析结果和后端实际使用的参数不一致。举个例子参数污染。请求长这样GET /search.php?id1id1 union select 1,2,3WAF按标准解析方式取第二个id值也就是1 union select 1,2,3命中规则拦截。但某些后端框架在取参数时只取第一个value——第一个id是干净的1于是请求被放行后端拿到的却是攻击者拼好的恶意SQL。反过来也一样有些框架取的是最后一个。WAF按自己的规则取后端按框架的规则取两者取的不是同一个值校验就形同虚设。再比如Content-Type伪装。有些WAF只对application/x-www-form-urlencoded这种格式的POST消息体做body检测但攻击者把Content-Type改成text/plain或者multipart/form-data一部分WAF直接放弃了body解析参数内容完全不检查就放行了。而后端框架对这些Content-Type是照单全收的照样把里面的数据拼进SQL语句。这块儿的加固方案需要有一定的对齐意识。一是确定后端框架取参的规则在WAF侧做参数取值顺序对齐二是对Content-Type缺失或不常见的请求体一律按最高安全级别处理而不是跳过主体检测三是在有条件的情况下用云WAF或Nginx层面的统一解析替代各业务系统自带的碎片化中间件解析。4. 从绕过视角看防御规则加固与验证的实操建议4.1 搭一个能复现的测试环境没有靶场谈绕过就是空谈。搭建方式有很多种我建议新手走一条成本最低的路线本机装一个开源WAF作为前置节点配置好基础规则。拿一个SQL注入靶场比如常见的注入练习环境作为后端。用Burp Suite或者任何能改包的工具手动构造不同类型的请求逐一测试。测试时不要上来就找复杂手法按顺序来先测大小写变形再测注释符混搭再测编码替换最后测参数污染。每测一条就在日志里看一眼是否命中规则、命中的是哪条规则这样日积月累你会逐渐摸清规则库的覆盖边界也会更清楚规则层面绕过到底绕的是什么。注意所有测试都必须在本地靶场环境中进行。千万不要对着别人的线上系统做这类验证那既没有任何技术意义也是明确违规的行为。4.2 规则加固的核心顺序踩过不少坑之后我总结了五条规则加固经验按优先级排列**第一优先级参数化查询/预编译。**这不是WAF规则但比任何规则都有效。SQL注入的本质是数据和代码混在了一起参数化查询让数据走专门通道进入SQL结构注入直接失去存在的前提条件。所有新增的业务代码强制走ORM或预处理语句这步做到位80%的注入风险在一开始就不存在了。**第二优先级统一解码与归一化。**确保WAF最终去匹配的字符串 后端真正执行时看到的字符串。把解码次数、解码顺序、大小写归一化流程固定下来不给双重编码留空间。**第三优先级分层配置规则。**对所有请求统一用一套宽松的基线规则对高危接口登录、查询、API接口再加严一层。别把同一个强度的规则平铺到所有接口上那样要么误杀太多要么力度不足。**第四优先级做语义分析型补充。**如果预算允许在正则规则之上增加一个语义分析引擎——它不去匹配长什么样而是判断这句话想干什么对同义变形的识别能力比纯正则有数量级的提升。第五优先级日志与告警的最后防线。不管你规则配得多好总会有一天被别人绕过去。关键不是你拦住了所有攻击而是被绕过的那一刻你能不能发现。数据库错误日志中的异常报错、返回包中的SQL错误特征、同一IP短时间内大量请求5xx状态码这些都是可能已经有人摸进来了的信号。有一次我们在日志里看到某接口密集返回500排查后发现是有人在用时间盲注测试。要不是日志开着这个行为可能被当成普通的服务抖动就忽略了。4.3 别把WAF规则层当万能保险箱我见过不少团队买了一套WAF做了个基础初始化配置就把安全工作全押在上面了。这种心态非常危险因为规则层面绕过的门槛其实不高——不需要0day不需要什么高深的底层漏洞只需要对SQL语法本身有一点灵活理解就够了。**WAF规则层的定位应该是护栏不是墙。**它能把自动化扫描器挡在外面把大部分随手试一下的投机者拦下来也能在系统本身的防护做得不够好时多争取一层缓冲。但它替代不了正当的编码规范、参数化查询、输入校验和权限控制。真正的防御纵深在于即使某一条线被绕过还有下一条线能兜住。5. 常见问题速查表与排坑实录最后整理一个实操中经常遇到的问题清单都是真实踩过坑的地方问题现象排查思路解决建议同样的payload有时候被拦有时候直接放行检查WAF是否有多个规则集并行匹配看命中的具体是哪个规则检查是否有请求频率限制和URL白名单导致部分路径免检把所有规则集改成交集都查不允许URL白名单覆盖安全规则改了大小写还是被拦目标WAF可能在匹配前已经做了大小写归一化大小写绕过的红利期正在消失改用注释符、编码变形、等价函数替代等混合手法防御方注意将规则匹配统一为忽略大小写为什么本地测试能过线上却被拦了线上WAF可能串联了多个安全产品不是某个单一WAF在做检测或者是不同设备的解码顺序不一样测试时尽量复现线上完整的链路拓扑至少先在Nginx层、WAF层、应用层三层分别做一次测试规则加了十几条还是被绕只用黑名单思路在堵漏洞很难覆盖完整攻击面优先做参数化查询和统一解码规则只在正常代码规范之上做兜底想验证编码绕过是否真的成功不要只看WAF日志还要看后端是否真正执行了payload用数据库日志或响应内容做执行确认请求被放行不等于注入成功再补一条容易被忽略的经验URL白名单和IP白名单请慎用。之前碰到过一个项目为了方便业务联调在WAF上加了某个路径的白名单结果这个路径正好是一个接口最后攻击者顺着白名单路径把整个库翻了。白名单不是不能加但一定要加白名单只豁免某项检测、不豁免全部检测的限制而且白名单路径要有明确的时效回收机制。还有一个容易被忽略的小细节检查你自己的安全产品日志是否被日志清洗或截断掩盖了。如果日志体量非常大很多WAF的日志系统会在写入时截断payload内容导致你在事后审计时根本看不到原始攻击载荷长什么样也就无从分析绕过路径。有条件的话把攻击payload的完整请求保存到一个独立存储里别和大日志混在一起。做了这么多年的安全对抗我自己最大的体会是规则层面的注入绕过说到底是模式匹配和语言表达之间的不对称。只要检测方还在依赖有限的正则去匹配无穷的语义表达绕过就永远存在。但这不意味着规则毫无意义——它能挡住没有思考能力的自动化扫描能挡住绝大多数投机尝试能为你发现异常争取时间。真正站在安全这一边的东西还是那几句话参数化查询、统一解码、最小权限、日志审计。把这四件事做好别人绕过了WAF也翻不出什么浪花来。
返回列表