应使用参数化查询而非字符串拼接,因SQLite的?占位符使参数独立序列化、不参与SQL解析,杜绝注入;Python 3.12虽无新防注入语法,但强化类型系统使手动转义等旧写法更易暴露问题。

直接用参数化查询,别拼字符串。Python 3.12 没新增防注入语法,但强化了类型系统和运行时契约,反而让旧的“手动转义”或“白名单过滤”写法更容易暴露问题。
为什么 sqlite3.execute() 的问号占位符不能被绕过
因为底层驱动在发送 SQL 到 SQLite 引擎前,会把参数单独序列化、不参与 SQL 解析;即使传入 "' OR '1'='1",它也只当一个字符串值,不会触发逻辑翻转。
- 错误做法:
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")—— 字符串拼接永远危险 - 正确做法:
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))—— 单个参数必须是元组,哪怕只有一个值 - 注意:
execute()只支持?占位符(SQLite),不支持%s或:name;后者是 psycopg3 或 SQLAlchemy 的风格
psycopg3 中命名参数与位置参数的实际差异
PostgreSQL 驱动更灵活,但命名参数(:name)要求键名必须严格匹配字典 key,而位置参数(%s)顺序错一位就全乱。3.12 下这两类调用本身没变,但如果你用了 TypedDict 做参数容器,mypy 和 runtime 的键校验现在更一致了——拼错 key 名会立刻报错,而不是静默查不到数据。
- 安全示例(命名):
cursor.execute("SELECT * FROM users WHERE id = :uid", {"uid": user_id}) - 安全示例(位置):
cursor.execute("SELECT * FROM users WHERE id = %s AND active = %s", (user_id, True)) - 危险信号:如果 IDE 提示
TypedDict缺少 required key,别加# type: ignore糊弄过去——3.12 会真正在运行时报KeyError
SQLAlchemy Core 在 3.12 下的隐性加固点
SQLAlchemy 自己做参数化,所以不依赖底层驱动的占位符机制。但 3.12 加强了 __getattribute__ 的调用边界,影响所有基于属性代理的封装层。如果你写了自定义的 UserQuery 类并重载了 __getattr__ 来动态生成 where 条件,现在必须确保它不干扰 select().where() 的原生行为,否则可能漏掉参数绑定。
立即学习“Python免费学习笔记(深入)”;
- 推荐写法:
stmt = select(User).where(User.name == name)—— 这是表达式树,不是字符串 - 避免写法:
getattr(User, field_name) == value动态构造时没做字段白名单校验 - 关键检查点:升级后跑一遍集成测试,重点看带
or_()、and_()的复合条件是否仍能正确绑定参数
最易被忽略的是日志和调试代码——很多人在 except 块里用 f"SQL failed: {sql} with {params}" 打印,结果把原始拼接语句又暴露了一次。3.12 不管你 log 什么,但攻击者只要拿到一条带漏洞的 log 就够了。


















