/**/ 更稳,因是 mysql 原生注释语法,版本≥5.0 即可执行且不依赖编码上下文;%0a 易被 web 服务器或多次解码处理截断或标准化为普通空格而失效。

空格被过滤时,/**/ 和 %0a 哪个更稳?
多数 WAF 用 \s(即 [\r\n\t\f\v ])匹配空格,但实际会漏掉 %0b、%0c、%a0 这类非标准空白符。而 /**/ 是 MySQL 原生注释语法,只要数据库版本 ≥ 5.0 就能执行,且不依赖字符编码上下文。
实操建议:
-
/**/在 union、select、from 等关键字之间插入最可靠,例如:union/**/select/**/1,2,database() -
%0a(换行)在 GET 请求中容易被 Web 服务器(如 Nginx)提前截断或标准化,尤其当后端用trim()或urldecode()多次处理时,%0a可能变成普通空格再被正则干掉 -
%a0(NO-BREAK SPACE)绕过\s的概率高,但某些 PHP 版本(如 8.1+)的mb_ereg_replace()默认启用 Unicode 模式,会把它识别为空白——得先urlencode()再发包
正则写成 /\s+/ 为什么拦不住 %09?
因为 %09 是 URL 编码后的水平制表符,在 HTTP 层未解码前只是四个 ASCII 字符:%09,根本不在 \s 范围内。WAF 若在请求体解码前做正则匹配,%09 就是合法字符串;若在解码后匹配,又得考虑双 URL 编码(如 %2509)和编码混淆(如 + 被当成空格)。
常见错误现象:
- WAF 配置了
deny \s,但攻击者发id=1%09union%09select成功执行 - 后端用
urldecode($_GET['id'])再传给 SQL,WAF 却只对原始参数做preg_replace('/\s+/', '', $input),导致绕过 - 某些 WAF 把
%00当作终止符截断,但 MySQL 支持\N表示 NULL,可替代空格出现在from\Nadmin中
如何让空格绕过失效?关键在解码时机与字符集
真正有效的防御不是堵单个字符,而是控制输入进入 SQL 前的状态。MySQL 的 sql_mode 若含 STRICT_TRANS_TABLES,会对非法空白符报错;而客户端连接时指定 charset=utf8mb4 后,%a0 才会被正确识别为 U+00A0,否则可能被当作乱码丢弃。
实操建议:
- WAF 必须在完成所有解码(URL decode、Unicode decode、+→space)后再做正则匹配,否则永远追不上编码变体
- 应用层应使用预编译语句(
prepare+bind_param),此时空格是否被绕过已无关紧要 - 若必须拼接 SQL,可用
mysqli_real_escape_string(),它对%00、%a0等均做转义,但注意该函数依赖当前连接字符集设置
preg_replace('/\s+/', '', $id) 这种写法本身就有漏洞
它只删空白,却不管括号、反引号、内联注释这些 MySQL 允许的语法糖。比如 union(select 1,2) 不含空格,但括号内仍可执行;from`information_schema`.`tables` 用反引号绕过空格和关键字检测;/*!50000select*/ 则利用 MySQL 特有注释触发执行。
更危险的是,这种正则会破坏正常业务数据:用户昵称含中文全角空格(U+3000)或 emoji 前后空白,也会被误删,导致逻辑错乱。
所以别迷信“删空格”,重点看三点:输入是否进 SQL、是否走预编译、数据库是否开严格模式。其他都是补丁。











