因为PHP的base64_decode()对非法字符静默跳过,而WAF未模拟该容错解码行为,导致其规则匹配的是污染前字符串,后端却还原出真实恶意payload,造成检测视图不一致。

Base64编码绕过WAF时,PHP后端为什么可能“放行”?
因为PHP的base64_decode()函数在遇到非法字符(如@、!、%)时默认会静默跳过,不报错也不中断解码。WAF若只做简单字符串匹配(比如查union select),而没模拟PHP这层行为,就看不到真实还原后的SQL payload。
常见错误现象:?id=MTIz能正常访问,?id=VVNIU0VMSUVDVA==(UNIONSELECT的base64)却被拦截——说明WAF做了base64检测;但?id=U@!NIONSE@!LECT却成功执行,暴露了它没做“容错解码”。
- 不要只检查原始参数是否含
base64_字样,要实际调用base64_decode($input, true)(PHP 8.2+)并捕获ValueError来判断是否为有效编码 - 对解码后的内容再做一次WAF规则扫描,而不是仅扫原始参数
- 若业务逻辑允许,可限制base64输入长度(如超过128字符直接拒绝),避免长payload绕过内存检测
十六进制编码(0x…)绕过的关键检测点
MySQL原生支持0x414243这种写法表示字符串ABC,攻击者常把' or 1=1 --转成0x27206f7220313d31202d2d,绕过对单引号和空格的规则。
容易踩的坑:WAF只正则匹配0x[0-9a-fA-F]+就拦截,结果把合法的十六进制ID(如user_id=0x1a2b)也误杀。
- 检测必须结合上下文:只有出现在SQL关键字附近(如
WHERE、AND、OR之后)的0x...才需重点审查 - MySQL中
0x后必须是偶数位十六进制字符,奇数位(如0x123)会报错,可用此做快速过滤 - 注意
0x与空格/括号之间无强制分隔,WHERE0x27=0x27也是合法语法,WAF不能只靠空格切分token
如何让WAF真正“看见”解码后的内容?
核心不是堵死所有编码形式,而是统一解码视图。WAF与后端必须使用相同的字符集、解码顺序、容错策略,否则永远存在“保安看单号,收件人拆包裹”的错位。
- 对URL参数先做
urldecode(),再做utf8_decode()(若后端用GBK,此处应为mb_convert_encoding(..., 'GBK', 'UTF-8')) - 对疑似base64字段,用
base64_decode($s, true)尝试解码;失败则保留原值;成功则将解码结果加入检测队列 - 禁用多层嵌套编码(如
base64(base64('union'))),可在WAF层限制base64解码深度≤1 - 记录解码链路日志:原始值→第一次解码→第二次解码→最终送入规则引擎的字符串,便于回溯绕过路径
为什么“加干扰字符”在Base64绕过里特别危险?
像U@!NIONSE@!LECT这类变体,不是所有WAF都能识别。有些规则引擎会把@!当作噪声过滤掉再匹配,有些则直接按字面串查——结果就是同一套规则,在不同WAF版本或配置下表现不一致。
更隐蔽的是PHP的base64_decode()对末尾填充字符=的宽松处理:UU5JT1...=和UU5JT1...(缺=)可能都解出相同结果,但WAF若严格校验base64格式,就会漏掉后者。
真正难防的不是编码本身,而是WAF与业务代码在“什么算合法输入”上缺乏共识。比如后端接受trim($input)后再解码,WAF却没做trim,那 UU5JT1... (首尾空格)就能穿过去。











