REGEXP 拦截危险 SQL 片段不靠谱,因其仅做字符串匹配,无法处理上下文敏感、注释绕过、大小写混写等攻击,且不支持高级正则特性;真正安全方案是应用层参数化查询+白名单校验。

MySQL 中用 REGEXP 拦截危险 SQL 片段不靠谱
直接用 REGEXP 校验用户输入的 SQL 合法性,本质上是在做「字符串模式匹配」,而 SQL 是上下文敏感的语言——SELECT 1 安全,SELECT 1 FROM users WHERE id = '1' OR '1'='1' 却可能绕过正则。更麻烦的是,REGEXP 在 MySQL 里不支持 PCRE 的高级特性(比如负向先行断言),也很难覆盖注释、大小写混写、空格/制表符绕过、Unicode 同形字等常见绕过手段。
实操建议:
- 别把
REGEXP当 SQL 解析器用,它连基本的括号嵌套都识别不了 - 如果只是想防最粗浅的
UNION SELECT或; DROP TABLE,可以用REGEXP 'UNION[[:space:]]+SELECT|;[[:space:]]*DROP'做初级过滤,但必须配合后续更严格的处理 - 注意 MySQL 8.0+ 默认区分大小写,
regexp 'union select'不会匹配Union Select;加BINARY或用LOWER()统一转换再匹配
真正该拦截的位置:应用层预处理 + 参数化查询
SQL 合法性校验不是数据库的事,是应用代码的事。重点不在“这段文本像不像 SQL”,而在“它会不会被拼进 SQL 字符串里执行”。
实操建议:
- 所有用户输入,只要参与 SQL 构建,必须走参数化查询:
cursor.execute("SELECT * FROM users WHERE name = %s", [user_input])(Python/psycopg2)或conn.prepareStatement("SELECT * FROM users WHERE id = ?")(Java/JDBC) - 如果业务真需要动态表名/字段名(极少数场景),必须白名单校验:
if user_table not in ['orders', 'products']:raise ValueError - 禁用字符串格式化拼接 SQL:
"WHERE id = " + str(user_id)或f"WHERE id = {user_id}"是硬伤,REGEXP再强也救不回来
REGEXP 能起作用的有限场景
它只适合做「输入清洗前置哨兵」,比如拒绝明显非法字符、长度超限、或强制要求符合某种 DSL 格式(非 SQL)。例如日志查询接口允许用户输简单条件:status=200 AND method=GET,这时可用 REGEXP 控制语法边界。
实操建议:
- 限制输入长度:
CHAR_LENGTH(user_input) > 500直接拒掉,避免正则回溯爆炸 - 只允许可信字符集:
user_input REGEXP '^[a-zA-Z0-9_= &|()\-\+\s]+$',但注意空格和括号本身也可能被滥用 - MySQL 5.7 及以前,
REGEXP性能差,高并发下易成瓶颈;8.0+ 改用REGEXP_LIKE()并加索引前缀字段可稍缓
为什么你看到的“SQL 注入正则黑名单”几乎都失效了
因为攻击者不按套路出牌。一个真实案例:'/**/UNION/**/SELECT/**/1,2,3-- ' 用注释绕过空格检测;se%6cect(URL 编码)在应用层解码前就进了 DB;甚至用 CONCAT('SEL','ECT') 动态拼函数名。
实操建议:
- 别维护自己的注入关键词黑名单,
REGEXP匹配SELECT|INSERT|UPDATE|DELETE|UNION|EXEC|DECLARE|WAITFOR看似全面,实则漏得彻底 - MySQL 自带的
sql_mode=STRICT_TRANS_TABLES或NO_AUTO_CREATE_USER对输入合法性毫无帮助,它们管的是语义合规,不是安全 - 真正要检查 SQL 结构,得用专门解析器,比如 Python 的
sqlparse库做语法树分析,但这仍不能替代参数化——解析合法 ≠ 执行安全
复杂点在于:SQL 输入是否“合法”,取决于它最终怎么被执行,而不是它看起来像什么。正则只能扫一眼表面,而漏洞藏在执行路径的缝隙里。










