为什么要用参数化查询?ORM 是不是自动安全?哪些位置参数化帮不上忙?这篇不演示攻击,只用一个名字里带单引号的正常用户,看拼接与绑定在真实的 PostgreSQL 里各自发生了什么。
f-string 拼接 → ProgrammingError: syntax error at or near "Brien"
绑定参数 :n → [(1,)]
发给数据库的语句与参数: ('select id from "user" where name = %(n)s', {'n': "O'Brien"})
ORM 表达式 → [(1,)]
发给数据库的语句与参数: ('SELECT "user".id FROM "user" WHERE "user".name = %(name_1)s::VARCHAR', {'name_1': "O'Brien"})
结论:引号被当作数据,不需要自己转义;拼接则把数据当成了 SQL 的一部分,正常输入也会出错排序列 'name' asc → ['50%_off', 'Ann', 'Bob', "O'Brien"] 排序列 'id' desc → ['Bob', '50%_off', 'Ann', "O'Brien"] 排序列 'password' asc → 拒绝:不允许按 'password' 排序 把列名当绑定参数 order by :c → ["O'Brien", 'Ann', '50%_off', 'Bob'] (只是按常量字符串排序,不会按列排序)
like('%50%_off%')(不转义,% 与 _ 当通配符)→ ['50%_off']
contains(term, autoescape=True) → ['50%_off']
发出的语句: HERE ("user".name LIKE '%%' || %(name_1)s::VARCHAR || '%%' ESCAPE '/') {'name_1': '50/%/_off'}
in_([...]) → ["O'Brien", 'Ann', 'Bob'] | 语句: _1)s::VARCHAR, %(name_1_2)s::VARCHAR, %(name_1_3)s::VARCHAR)PG 日志: 2026-10-02 14:00:15.243 CST [23880] STATEMENT: select id from "user" where name = 'O'Brien' PG 日志: execute <unnamed>: select id from "user" where name = $1
本篇只做防御向的验证:没有构造任何利用语句。