sqlalchemy orm 通过参数化查询默认防注入,但拼接原始sql、f-string插值、未校验的外部输入(如headers/cookies)及表名列名动态化会绕过防护,必须全程使用属性比较、text()命名参数和白名单校验。

直接用 session.query().filter() 就能防注入,但别手写 query 字符串
SQLAlchemy ORM 默认所有查询都走参数化执行,filter(User.name == user_input) 这种写法底层自动转成带占位符的预编译语句,用户输入永远不会被当 SQL 代码解析。但一旦你绕过 ORM、拼接原始 SQL,比如 session.execute(f"SELECT * FROM users WHERE name = '{name}'"),防线立刻崩溃。
常见错误现象:搜索接口返回异常多条记录、管理员账号被绕过、页面报错里出现数据库字段名或表名——这些往往是注入已生效的信号。
- ORM 查询必须全程使用属性比较(
User.id == x),不能把变量塞进字符串 - 避免混用:不要在 ORM 查询里夹杂
.text()或session.execute()+ 字符串拼接 - 如果必须用原生 SQL,只允许用
session.execute(text("..."), {"param": value})形式传参
session.execute(text(...)) 怎么写才安全
原生 SQL 在 SQLAlchemy 中不是不能用,而是必须配合 text() 和命名参数。直接传字符串进去等于开门揖盗。
错误示例:session.execute(f"SELECT * FROM products WHERE category = '{cat}'") —— 黑客输 toys' OR '1'='1 就查出全表。
正确写法:
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
from sqlalchemy import text
stmt = text("SELECT * FROM products WHERE category = :cat")
result = session.execute(stmt, {"cat": cat}).fetchall()
-
:cat是命名占位符,必须和字典键名严格一致 - 不支持
?问号占位符(那是 SQLite 原生驱动的语法,ORM 层不认) - 表名、列名不能参数化——它们属于 SQL 结构,必须硬编码或通过白名单校验
为什么 query.filter(User.name.like(f"%{keyword}%")) 很危险
这个写法看着像 ORM,实则是把用户输入直接插进字符串再交给 SQL 解析。like() 方法本身不处理参数化,它只是生成 SQL 片段,而 f-string 已经在 Python 层完成拼接,数据库收到的就是完整恶意语句。
攻击者输入 %' UNION SELECT password FROM users--,最终执行的可能是:SELECT * FROM users WHERE name LIKE '%'' UNION SELECT password FROM users--%'。
- 正确替代方案:
filter(User.name.like(f"%{keyword}%"))→ 改为filter(User.name.like("%" + keyword + "%"))并确保keyword已过滤掉单引号、反斜杠、百分号等通配符 - 更稳妥做法:用
ilike()+ 参数化绑定,如filter(User.name.ilike(f"%{keyword}%"))不行,必须用text()配合参数 - 若需模糊搜索,优先考虑数据库全文检索(PostgreSQL 的
to_tsvector)或专用搜索引擎(Elasticsearch)
容易被忽略的“非查询”入口:URL 路径、请求头、Cookie
开发者常紧盯表单和查询参数,却放行了 request.headers.get("X-User-ID") 或 request.cookies.get("session_token")。只要这些值进了 SQL 查询,风险完全等同。
典型场景:按请求头里的租户 ID 查询数据、用 Cookie 中的 token 查用户权限——这些都不是“用户主动输入”,但一样可被篡改。
- 所有外部输入源都要一视同仁:URL path、query string、form data、JSON body、headers、cookies、甚至 gRPC metadata
- ORM 查询中,哪怕只用于
filter()的字段,也必须经过类型检查(如int(user_id))或白名单验证(如if role not in ["admin", "user"]) - 日志里打印 SQL 时,别直接输出
str(stmt),它会暴露未绑定的原始语句,泄露逻辑结构
ORM 的防护能力只覆盖它控制的查询路径;一旦你手动拼字符串、调用 execute() 时不传参数字典、或把用户输入当 SQL 结构用,就等于亲手拆掉最后一道墙。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










