参数化查询防注入的根本原因是分离SQL结构与数据,数据库预编译语句后仅将参数作为二进制值绑定执行,不参与词法/语法解析;拼接字符串(如f-string)仍使输入进入SQL解析流程,导致注入。

参数化查询不是“转义”,而是让数据库彻底不解析参数
它防注入的根本原因,不是把 ' 变成 \',而是让数据库在编译阶段就锁死 SQL 结构,用户输入只作为二进制数据传入执行阶段——压根不会走词法/语法分析器。哪怕你传入 "admin' OR 1=1 --",它也只是个长度为17的字符串值,不会被切分成 token,更不会参与生成执行计划。
常见错误现象:以为用了 .format() 或 f-string + 单引号包裹就是“安全拼接”,比如:
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")这和直接拼接没区别,输入仍会进入 SQL 解析流程,' 一出现就提前闭合字符串,后面全是攻击者可控的语句。
不同驱动的占位符写法差异不影响底层机制
占位符只是语法糖,背后都是预处理语句(prepared statement)+ 绑定变量(bound parameter)。关键看驱动是否真正走 prepare → bind → execute 流程,而不是字符串替换。
-
sqlite3和PyMySQL用%s,参数必须是元组或列表:cursor.execute("WHERE id > %s", (42,)) -
psycopg2支持%s和命名式%(name)s:cursor.execute("WHERE status = %(s)s", {"s": "active"}) -
SQLAlchemy的text()需显式标记参数:text("SELECT * FROM :table WHERE id = :id").bindparams(table="users", id=1)—— 注意这里:table仍是非法的,仅:id有效
性能影响:首次执行稍慢(因编译),但后续复用执行计划,反而比每次编译快;兼容性上,所有主流驱动都支持,无需额外安装扩展。
表名、字段名、ORDER BY、LIMIT 这些地方参数化必然失败
数据库语法规定:这些属于 SQL 结构(syntax),不是值(value),驱动层直接报错。例如:
cursor.execute("SELECT * FROM ?", ("users",)) # sqlite3.Error: near "?": syntax errorcursor.execute("ORDER BY ? DESC", ("created_at",)) # 不生效,或报错必须白名单校验,不能靠正则过滤或截断:
- 动态表名:用字典映射,如
table_map = {"user": "tbl_users_v2", "log": "tbl_logs_2026"},只接受 key 存在才拼接 - 排序字段:限定为
["id", "name", "updated_at"],用if field not in allowed_fields:拦住 - LIMIT 数值:强制
int(limit)后限制范围,如max(1, min(100, int(limit))),避免LIMIT 9999999拖垮查询
容易踩的坑:有人对字段名做 re.sub(r"[^a-zA-Z_]", "", user_input),但双写绕过(oorder by)、宽字节编码、注释干扰等仍可能穿透。
ORM 并不自动免疫,raw query 和表达式是高危区
Django ORM 的 .filter(name=x)、SQLAlchemy 的 .where(User.name == x) 默认走参数化,但一旦出现以下写法,防线立刻失效:
- Django:
Model.objects.extra(where=["name = '%s'" % user_input])、.raw("SELECT * FROM users WHERE name = '%s'" % user_input) - SQLAlchemy:
session.execute(text(f"SELECT * FROM users WHERE {field} = '{value}'"))、query.filter(text("status = '" + status + "'"))
这些接口绕过了 ORM 的参数绑定机制,等价于字符串拼接。检查代码时,重点盯住所有含 raw、text、extra、filter(Q()) 中嵌套字符串的地方。
最易被忽略的一点:即使全程用参数化,如果数据库账号权限过大(如赋予 DROP TABLE 或 EXECUTE),攻击者仍可能通过报错回显、联合查询等方式探测结构,甚至读取系统表——参数化只解决“注入执行”,不解决“越权访问”。

















