URL编码和双重编码常失效,因WAF在解析阶段即完成多轮解码与规范化;真正有效的是利用WAF盲区如Unicode编码%u0027、十六进制0x27或数据库特有语法如MySQL条件注释/*!50700 SELECT*/。

SQL注入绕过WAF时,URL编码和双重编码为什么常失效
多数WAF(如ModSecurity、云WAF)在请求解析阶段就完成了URL解码,且会做多轮规范化处理。你发%2527(即%27的二次编码),WAF解码后仍得到',规则照样触发。
- 真正有效的编码需匹配WAF解析链的“盲区”:比如某些WAF不处理
%u0027(Unicode编码),但MySQL不认这个,得换用数据库支持的格式 - MySQL支持
0x27(十六进制字面量)、'(在特定上下文中被解析为字符),但前提是上下文允许十六进制字符串参与拼接 - PostgreSQL支持
E'\x27',而部分WAF规则未覆盖E''语法 - 别硬套
%27→%2527,先确认目标WAF是否做多次解码——用id=1%2527%2520OR%25201%253D1%2520--测试返回是否报错,若直接拦截,说明它已解到原始字符
用注释符和空格变形绕过关键词检测
WAF常靠正则匹配UNION SELECTOR 1=1这类模式,但MySQL、MariaDB对空白符和注释极其宽容,且不同版本支持的注释语法不同。
- 用
/**/*代替空格:例如SELECT/**/username/**/FROM/**/users,WAF正则若只锚定SELECTs+FROM就会漏掉 - MySQL 5.7+支持
/*!50700 SELECT*/——条件注释,内容只在指定版本及以上执行,WAF规则极少覆盖这种语法 - 用
%09(tab)、%0a(换行)、%0c(换页)替代空格,比+更隐蔽;但注意PHP$_GET默认会将+转为空格,而%09保留原义 - 避免用
--(带空格),改用#或/*abc*/,部分WAF对#过滤不严,尤其当它出现在参数末尾时
利用函数别名与等价表达式替换敏感操作符
WAF若用黑名单过滤UNIONSELECTAND,可转向语义等价但字面不同的写法,关键是让数据库能执行、WAF又不认得。
-
AND可换为&&(MySQL)、AND 1→LIKE 'a'='a'(绕过布尔型检测) -
UNION SELECT可拆成UNION/*foo*/SELECT,或用UNION (SELECT ...)加括号改变语法树结构 - 列数探测时不用
ORDER BY 100暴力猜,改用SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c...——WAF很少规则覆盖嵌套SELECT中的逗号分隔 - 读文件不用
LOAD_FILE(),改用SELECT LOAD_FILE(0x2f6574632f706173737764)(十六进制路径),既绕过字符串检测,又避免单引号
为什么堆叠注入(Stacked Queries)在多数场景下根本走不通
WAF拦不住;不代表后端能执行堆叠——关键在数据库驱动和API层是否允许多语句。PHP的mysql_query()禁用堆叠,mysqli_multi_query()才支持,而绝大多数Web应用没开这个选项。
- 即使WAF放行
id=1; DROP TABLE users--,PHP收到后调用mysqli_query($conn, $sql)会直接报错Commands out of sync - Python的
pymysql默认关闭multi=True,Go的database/sql也不支持多语句,除非显式配置 - 唯一常见可行场景是使用
sqlmap --batch -p id --dbms=mysql --technique=S时,它会先用布尔/时间盲注确认存在性,再尝试堆叠——但成功率取决于后端代码,不是WAF松动就能成 - 别把
; SELECT当万能钥匙,先看报错信息里有没有mysqli_num_rows()或pg_fetch_array()这类函数名,它们暗示了底层驱动类型
实际绕过成败,取决于WAF规则粒度、数据库版本、驱动配置三者交集。最常被忽略的是:WAF可能放过SELECT,但后端代码用intval()强制转整型,导致所有字符型payload从第一步就被截断——得先确认输入是否真的进了SQL拼接环节。

















