orm中raw()、text()、${}等接口绕过参数化,直接拼接sql导致注入;order by、表名等结构部分无法参数化,须白名单校验;extra()/func()/mybatis ${} 是高危隐藏点,需禁用或严格过滤。

raw()、text()、${} 这些接口一用就失效
ORM 的标准查询方法(如 filter()、where()、find())默认走预编译 + 参数绑定,天然防注入。但 raw()(Django)、text()(SQLAlchemy)、DB::raw()(Laravel)、${}(MyBatis)这类接口,本质是绕过 ORM 查询编译器,直接把字符串交给数据库驱动执行。
常见错误现象:
-
MyModel.objects.raw(f"SELECT * FROM t WHERE name = '{name}'")—— f-string 拼接,参数化完全没机会介入 -
session.execute(text("SELECT * FROM users WHERE id = " + user_id))—— 字符串拼接,等同于裸 SQL -
<script>SELECT * FROM user WHERE name = '${name}'</script>——${}是字符串替换,不是参数化
排查建议:
- 全局搜索项目中所有
raw(、text(、execute(、query_raw(、${出现的位置 - 检查这些调用是否含用户输入(
request.GET、request.POST、URL path、header 等) - 确认参数是否通过
params=或字典传参,而非拼进字符串里
ORDER BY、表名、字段名根本不能参数化
数据库协议只允许「值」参与参数化(如 WHERE name = ?),而 ORDER BY、表名、列名、GROUP BY、LIMIT offset 这些结构部分,在 SQL 解析阶段就必须确定,驱动层根本不支持运行时占位。
典型错误写法:
-
.order_by(request.GET.get("sort"))—— 攻击者传id; DROP TABLE user; --会直接进 SQL -
cursor.execute(f"SELECT * FROM {table_name}")—— 表名拼接,params=完全无效 -
filter(**{field: value})—— 键field是动态字段名,若未白名单校验,可传__class__或嵌套路径如profile__user__is_staff
排查建议:
- 搜索所有
order_by(、extra(tables=、annotate(...template=、Func(...template=调用 - 检查是否对
sort、table_name、field等变量做了白名单判断,例如:if sort not in ["created_at", "score"]: - 避免用
getattr(model, field_name)之前不校验field_name是否为模型真实字段
extra()、annotate()、Func() 中的 template 和 decimals 是隐藏注入点
Django 的 extra() 和 annotate() 配合 Func() 或自定义 template,属于“内部控制层”,不走参数绑定,也不做任何转义。只要用户输入进了 template 字符串或 extra 的 where 列表,就等于直插 SQL。
危险示例:
-
.extra(where=["name LIKE '%" + keyword + "%'"])——keyword未过滤即拼入 -
annotate(score=Func('score', template="%(function)s(%(expressions)s, %(decimals)s)", extra={'decimals': request.GET.get('precision')}))——decimals若为"2); DROP TABLE user; --",会完整进 SQL
排查建议:
- 禁用
extra()(Django 已弃用),改用Q()或Case/When - 检查所有
Func()、Cast()、Coalesce()调用,确认template中不含用户输入,且extra字典值来自白名单或硬编码 - 对
request.GET.get('precision')类参数,强制转为整数并范围限制(如int(p) in range(0, 6)),而非直接塞进 template
MyBatis 的 ${} 和 #{} 混用极易踩坑
MyBatis 中 #{} 触发 PreparedStatement 绑定,安全;${} 是纯字符串替换,等同于 Python 的 f-string,完全不经过参数化。开发者常在需要动态表名或排序字段时手抖选错。
高危组合:
-
SELECT * FROM ${tableName} WHERE id = #{id}—— 值安全,但表名已沦陷 -
ORDER BY ${sortField} ${sortDir}—— 两个${}全部暴露,可注入注释、分号、子查询
排查建议:
- 全局搜索项目中所有
${,逐个确认是否必须用(如分库分表场景),并配套白名单校验逻辑 - 检查是否对
sortField做了严格枚举:if sortField not in ["name", "created_at"]: throw - 禁止在
${}内做任何格式化或拼接,例如${prefix}_user——prefix必须先校验再拼
真正难的从来不是识别哪行代码在拼 SQL,而是搞清哪些位置连「参数化」这个选项都没有——表名、排序字段、JSON 路径、函数模板里的占位符……这些地方没有银弹,只有白名单和硬编码。漏掉一个,整条链路就断了。











