SQL注入防护必须用参数化查询,禁用字符串拼接;表名列名等动态部分须白名单校验;ORM不自动免疫,原生SQL调用仍需参数化;正则过滤效果差,应作为审计辅助而非防御主力。

SQL注入过滤函数不能只检查关键词
靠简单字符串匹配 SELECT、UNION、OR 1=1 这类词,基本没用。攻击者会用大小写混写、URL编码、注释符绕过,比如 SeLeCt 或 %55NION 或 /**/UNION/**/SELECT。这种“黑名单式”过滤在真实场景中等于形同虚设。
实操建议:
- 永远优先用参数化查询(
PreparedStatement在 Java,pg_query_params在 PHP,cursor.execute(sql, params)在 Python) - 如果必须做输入清洗(如动态表名、排序字段),只允许白名单:比如排序字段只接受
['created_at', 'score', 'id'],其余直接拒掉 - 避免对用户输入做“替换敏感词”操作(如把
or替成空),这可能破坏正常语义,还容易被绕过
动态拼接 SQL 时哪些场景必须禁止拼接
不是所有 SQL 片段都能安全拼接。表名、列名、ORDER BY 子句、LIMIT 偏移量这些无法参数化的部分,最容易成为注入入口。
常见错误现象:
-
"SELECT * FROM " + user_input_table_name + " WHERE id = ?"→ 攻击者输users; DROP TABLE users-- -
"ORDER BY " + request.query.order_by→ 输id ASC, (SELECT password FROM users LIMIT 1)
实操建议:
- 表名/列名一律映射到预定义常量,例如
table_map = {'users': 'users', 'orders': 'orders'},查不到就报错 -
ORDER BY字段只允许从固定数组中取值,并显式指定方向:if order_col in ['name', 'email'] and order_dir in ['ASC', 'DESC'] - 不要试图“修复”非法输入,直接 400 拒绝
为什么 ORM 并不自动免疫 SQL 注入
很多人以为用了 Django ORM 或 SQLAlchemy 就万事大吉,但绕过 ORM 的写法很常见。只要调用了原生 SQL 接口,风险就回来了。
典型危险操作:
- Django:
Model.objects.extra(where=["name = '%s'" % user_input])或connection.cursor().execute("SELECT ... " + user_input) - SQLAlchemy:
session.execute("UPDATE users SET name = '" + name + "'")
实操建议:
- 禁用所有带字符串拼接的
execute()调用,改用带参数占位符的版本(session.execute(text("..."), {"name": name})) - 全局搜索代码库中的
.execute(、.raw(、.extra(,逐个确认是否用了参数化 - ORM 的
filter()、order_by()等方法本身是安全的,但一旦转成.query或.sql属性再拼接,就失效了
正则过滤或中间件统一拦截的实际效果很弱
有些团队在 Web 中间件里加一层“SQL 关键字检测”,看似省事,实则误导安全感。它既拦不住真正变形的 payload,又容易误杀合法输入(比如用户昵称含 union,评论里写 OR 逻辑)。
性能与兼容性影响:
- 每个请求都跑多条正则,尤其对长文本字段(如富文本内容),CPU 开销明显
- 不同数据库语法差异大(PostgreSQL 支持
$$字符串定界符,MySQL 有反引号列名),通用正则根本覆盖不全 - HTTP 请求头、Cookie、文件名等非 body 字段也可能带恶意内容,单靠 body 过滤漏报率高
实操建议:
- 这类中间件最多作为辅助日志审计手段,不能当防御主力
- 如果真要加,只记录疑似 payload 到告警系统,不阻断请求
- 重点投入在开发规范和 CI 检查上:比如用
grep -r "execute.*\".*\+.*\"" .扫描硬编码 SQL 拼接
最麻烦的点其实是——很多注入漏洞藏在旧代码的边缘逻辑里,比如导出功能拼接 CSV 字段名、搜索接口支持多表联合的“高级模式”。这些地方没人维护、测试覆盖低,反而最危险。










