真正匹配union select需覆盖大小写、空格干扰和注释插入三类绕过,正则应为(?i)union[\s%09%0a%0b%0c%0d\xa0](?:/**/|--+|#)?[\s%09%0a%0b%0c%0d\xa0]select,且必须加\b边界符并启用URL/十六进制解码预处理。

怎么写能真正匹配到 union select 这类组合关键字
正则必须覆盖大小写变体、空格干扰、注释插入这三类常见绕过,否则 UnIoN/**/SeLeCt 就能直接穿过去。不能只写 union\s+select,得用 (?i)union\s*(?:/\*\*\/|\s|#|--\s+)?\s*select 这种结构。
常见错误是漏掉中间的零宽断言或可选分隔符,导致只匹配干净请求。实际攻击里空格常被 %09、%a0、/**/ 替代,正则里得显式包含这些可能:
-
\s*要换成[\s%09%0a%0b%0c%0d\xa0]*才覆盖全控制字符 - 注释符号如
/**/、--+、#得作为非捕获组加入:(?:/\*\*\/|--\+|#) - 避免用
.*匹配中间内容,性能差还容易误杀;改用[^&\n\r]{0,50}限制长度
为什么开了规则却拦不住 ' OR 1=1 -- 这种基础 payload
多数 WAF 默认只扫描 $_GET 和 $_COOKIE,而现代应用大量使用 JSON POST 或表单提交,$_POST 和原始 body(php://input)根本没开扫描。PHPWAF 就需要手动设 $config['scan_post_data'] = true 和 $config['scan_raw_body'] = true。
另一个高频原因是规则加载路径错误。比如配置写了 rule_path = "/etc/phpwaf/rules",但实际文件在 /usr/local/etc/phpwaf/rules/sql_injection.rule,WAF 启动时日志会报 failed to open stream,但很多人不查日志就以为“开了等于生效”。
- 确认规则文件存在且 PHP 进程有读权限:
ls -l /path/to/rules/sql_injection.rule - 检查 WAF 日志是否出现
loading rule file成功提示 - 用
curl -X POST -H "Content-Type: application/json" --data '{"username":"admin' OR 1=1 --"}' http://test.com/login实测 POST 场景
哪些 SQL 关键字组合必须进正则,又不能误伤正常业务
只列单个词(如 select)必然误杀,必须抓语义组合。重点盯三类结构:
- 查询结构:
(?i)\b(select|union|insert|update|delete|drop|alter|create|truncate)\b.*?\b(from|into|table|database|where)\b - 逻辑运算:
(?i)\b(and|or|not)\s+[\w\p{L}]+[=\!]+(防id=1 and 1=1) - 危险函数:
(?i)\b(load_file|outfile|dumpfile|sleep|benchmark|version|user|database)\b
注意 \b 边界符必须加,否则 user_id 里的 user 会被误判;[\p{L}\w] 比 \w 更准,能兼容中文变量名场景。
绕过检测的典型变形怎么在规则里提前堵住
MySQL 的内联注释 /*!50001union*/、双写 SELselectECT、十六进制编码 0x756e696f6e 都是高频绕过手段。不能指望一条正则全防住,得拆成多层规则:
- 先解码再匹配:WAF 层需开启 URL 解码、hex 解码(如
0x[0-9a-fA-F]+→ 转为字符串再走主规则) - 对内联注释单独写规则:
/\*![0-9]{5,}(union|select|exec)/i - 双写检测用负向先行断言:
(?i)(u(?=ni))|(n(?=io))|(i(?=on))这类模式代价高,慎用;更稳的是匹配连续重复字符后接关键词,如([a-z])\1+(union|select)
最易被忽略的是 MySQL 版本注释执行特性——/*!50001select*/ 在任意 MySQL 版本下都会执行,但很多 WAF 规则只当普通注释放过。这条必须单独拎出来加 deny 动作。

















