sql注入在orm中基本消失是因为主流orm默认使用参数化查询,用户输入作为预编译参数传入而非拼接进sql字符串;一旦手动拼接sql、动态拼表名列名或绕过orm抽象层,风险即回归,须用白名单校验等安全措施。

SQL注入为什么在ORM里基本消失
因为主流ORM(如Django ORM、SQLAlchemy、TypeORM)默认使用参数化查询,WHERE条件里的变量不会拼进SQL字符串,而是交由数据库驱动以预编译参数方式传入。只要不手动拼接SQL,攻击者输' OR 1=1 --这类payload,只会被当做一个普通字符串值处理。
哪些写法会绕过ORM的安全机制
一旦你主动跳出ORM抽象层,风险立刻回归。常见踩坑点包括:
- 用
raw()(Django)、text()(SQLAlchemy)或queryRaw()(Prisma)执行手写SQL,且把用户输入直接插进字符串里 - 用
filter__sql(Django)或whereRaw()(Laravel Eloquent)时,把request.GET.get('sort')直接塞进SQL片段 - 用
format或%拼接表名/字段名——这些不能参数化,ORM也不拦你
例如:cursor.execute("SELECT * FROM %s WHERE name = '%s'" % (table_name, user_input)) —— 这已经不是ORM问题,是彻底放弃防护。
如何安全地动态指定表名或排序字段
表名、列名、ORDER BY子句无法用参数占位符,必须白名单校验:
- 硬编码可选值:
if sort_field not in ['created_at', 'title', 'status']: raise Http404() - 用
getattr(model, field_name)代替字符串拼接字段(Django/SQLAlchemy均支持) - 排序方向限制为
asc/desc,再映射到order_by()或asc()/desc()方法调用
别信“转义反引号”或“正则过滤字母数字”——攻击者能用Unicode变体、注释符绕过,白名单才是唯一可靠方案。
ORM不是银弹:还要防哪些“非注入但致命”的漏洞
参数化只解决注入,不解决逻辑越权或数据泄露:
-
User.objects.filter(id=request.GET.get('id'))—— 没校验登录态和权限,ID是整数但没做归属检查 -
Model.objects.all()[:1000]在未分页接口里暴露全量数据 - 用
extra(tables=['user'])(Django)引入额外表却没重命名字段,导致列名冲突或信息泄露
真正难的从来不是拼SQL,而是搞清“谁能在什么条件下看到什么数据”。ORM帮你挡住了最野的攻击,但业务规则得自己守。










