text()本身不防sql注入,必须配合命名占位符(如:name)和execute()的参数字典才能生效;动态标识符需白名单校验,in查询应交由sqlalchemy自动处理。

text() 里拼字符串就是自毁防火墙
很多人以为只要用了 text() 就算“进了 ORM 安全区”,结果写成 text(f"SELECT * FROM users WHERE name = '{name}'"),等于把注入入口亲手焊死在 SQL 字符串里。SQLAlchemy 不会扫描你 f-string 里的变量,它只认占位符——:name 或 ?,别的全是裸奔。
常见错误现象:用户传 name="admin' OR 1=1 --",查询直接返回全部用户;更糟的是传 "admin'; DROP TABLE users; --",在支持多语句的驱动(如某些 MySQL 配置)下真能删表。
- ✅ 正确写法:
text("SELECT * FROM users WHERE name = :name")+session.execute(..., {"name": name}) - ❌ 错误写法:
text("SELECT * FROM users WHERE name = '" + name + "'")、text(f"SELECT ... WHERE name = '{name}'")、text("SELECT ... WHERE name = %s" % name) - 注意:
text()本身不触发参数绑定,必须配合execute()的第二参数(dict 或 tuple)才生效
动态字段名(ORDER BY / GROUP BY)不能用 :param
:sort_col 在 ORDER BY :sort_col 中不会被解析为列名,而是当作字符串字面量,最终生成类似 ORDER BY 'email' 的无效语法,或更糟——被数据库当成常量处理,完全绕过排序意图。
这不是参数化失效,是 SQL 标准限制:标识符(表名、列名、函数名)无法参数化,只能靠白名单硬控。
- 字段白名单推荐用枚举或字典映射:
allowed_cols = {"email": "users.email", "created_at": "users.created_at", "status": "users.status"} - 校验逻辑必须写死:
if sort_by not in allowed_cols: raise ValueError("Invalid sort field") - 拼接时用 Python 字符串格式化(仅限白名单内值):
f"ORDER BY {allowed_cols[sort_by]} {direction}",其中direction也需白名单校验(%w{asc desc}) - 别信正则过滤:
/^[a-z_]+$/拦不住email ASC, (SELECT password FROM admins)
session.execute() 和 connection.execute() 的参数传法差异
两者都支持 text() + 参数字典,但底层事务和连接生命周期不同,影响错误表现和资源释放。最易踩的坑是:传参格式错位导致参数被忽略,退化为字符串拼接。
-
session.execute(text("... :x"), {"x": val})✅ 安全,推荐用于 ORM 管理的会话 -
engine.connect().execute(text("... :x"), {"x": val})✅ 安全,但需手动close() -
connection.execute("... %s", [val])❌ 错误:PostgreSQL/psycopg2 不认%s,SQLite 只认?,MySQL 才认%s;混用必报错或失效 - 统一建议:全项目只用命名占位符
:x+ dict 传参,兼容所有驱动,且可读性高
IN 查询别手写占位符串
报表类功能常需 WHERE id IN (?, ?, ?),有人手动拼 str_repeat 或 join 占位符,稍一出错就漏掉参数或语法错。SQLAlchemy 原生支持数组自动展开,无需手算个数。
- ✅ 推荐写法:
select(User).where(User.id.in_(user_ids)),user_ids是 Pythonlist,SQLAlchemy 自动转成参数化IN表达式 - ✅ 原生 SQL 场景:
text("SELECT * FROM users WHERE id IN :ids")+{"ids": tuple(user_ids)}(注意必须是tuple,部分驱动不接受list) - ❌ 危险写法:
f"IN ({', '.join(['%s'] * len(ids))})"+execute(sql, ids)—— 若ids为空列表,SQL 直接语法错误;若含非数字,仍可能被注入 - 前置校验不能少:
if not isinstance(user_ids, list) or not user_ids:→ 拒绝空/非数组输入
真正难的不是写对那行 text("... :x"),是守住白名单边界的那一行 if x not in ALLOWED —— 它不在 SQL 里,却决定整个查询是否可信。











