根本原因是SQL关键字本身是普通英文单词,正则缺乏上下文感知能力,只匹配字面值而无法判断是否处于SQL语法位置。

正则替换SQL关键词会误杀正常数据,根本原因是什么
因为SQL关键字(如 select、and)本身是普通英文单词,出现在用户名、产品名、日志内容里极其常见。比如用户叫 Andy,你用 /and/i 一匹配就干掉;再比如字符串 O'Reilly 里的单引号,被粗暴删掉后数据就损坏了。正则没上下文感知能力,它只管“有没有”,不管“是不是在SQL语法位置上”。
哪些正则写法看似防注入实则危险
这类代码在老项目里高频出现,但全是纸老虎:
-
preg_replace('/(union|select|insert|drop)/i', '', $input)——union出现在reunion或unions里照样被删 -
str_replace(['"', "'", ';'], '', $_GET['q'])—— 完全忽略无引号上下文(如ORDER BY 1 ASC; DROP TABLE users中的分号在空格后才生效) -
preg_match('/\b(select|update)\b/i', $input)——\b在中文或特殊字符边界失效,SELECT%20FROM里的 URL 编码绕过零成本
如果非要用正则做轻量过滤,该怎么写
仅限非SQL上下文,且必须满足两个前提:输入字段有明确格式约束、不参与任何数据库拼接。例如校验用户名只能含字母数字下划线:
preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username) —— 这种锚定开头结尾、限定字符集和长度的模式才是安全的。
立即学习“PHP免费学习笔记(深入)”;
若硬要扫关键词,至少加空格/边界保护,并只用于告警不用于拦截:
- 匹配
select必须前后是空白或语句边界:/[\s(]select\s/i(注意不能用\b,因 SQL 中常跟括号或逗号) - 避免全局替换,改用
preg_match()记录日志或触发人工审核,而不是preg_replace()直接破坏原始值 - 永远不单独依赖它——哪怕加了空格保护,
SeLeCt+%00+/**/组合依然能绕过
PDO预处理才是唯一不可妥协的防线
正则过滤后,所有动态值仍必须走 prepare() + bindValue() 或 execute() 参数绑定。否则正则漏掉的变体(如双写 selselectect、宽字节 %df%27)会直接执行。
关键配置缺一不可:
-
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false)—— 模拟预处理等于没用 -
$pdo->exec("SET NAMES utf8mb4")或连接DSN里加;charset=utf8mb4—— 防宽字节注入 -
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)—— 静默失败会让问题藏得更深
真正进数据库前,没有参数绑定,就没有安全可言。正则最多是日志里打个标记,别让它碰查询逻辑。



















