参数化查询是唯一能从执行机制上切断SQL注入链路的手段,它通过数据库协议层将SQL模板与参数数据物理分离,使用户输入恒为纯数据、永不参与语法解析;而addslashes()等转义函数仅在应用层做字符串替换,无法改变拼接本质,易被宽字节、Unicode及非字符串上下文绕过。

参数化查询不是“比过滤好一点”的替代方案,而是唯一能从执行机制上切断注入链路的手段;所有基于过滤、转义、正则匹配的方案,都默认把用户输入当作需要“擦除危险字符”的文本,但数据库真正要处理的是语法结构——这根本不是同一层的问题。
为什么 mysql_real_escape_string() 或 addslashes() 无法修复注入
这类函数只在应用层做字符串替换,不改变 SQL 的拼接本质。只要代码里还存在 "WHERE name = '" + user_input + "'" 这种模式,攻击者就能绕过:
- MySQL 宽字节注入:输入
%df%27(GBK 编码下%df和后续%27组合成合法多字节字符,使单引号逃逸) - Unicode 编码绕过:PostgreSQL 支持
U&'\+0027'表示单引号,正则很难全覆盖 - 非字符串上下文完全无效:对
ORDER BY字段、LIMIT偏移量、表名做addslashes()没有意义——它们根本不是字符串值,而是语法节点
参数化查询如何让数据库“拒绝解析”恶意内容
它不靠“识别并删掉危险符号”,而是让数据库引擎在协议层就区分“模板”和“数据”。例如:
你传入 admin' OR '1'='1,数据库收到的不是拼接后的完整 SQL 字符串,而是两部分:
— 预编译语句:SELECT * FROM users WHERE username = ?
— 独立二进制参数:admin' OR '1'='1(类型为 TEXT,长度固定)
此时单引号只是字符串里的一个字节,不会触发词法分析重开字符串边界,更不会被当作语法分隔符。哪怕输入是 ; DROP TABLE users; --,也只会查一个用户名叫这个的用户。
关键点:
- 占位符(
?、%s、:name)必须由驱动原生支持,不能用str.replace()模拟 -
cursor.execute("WHERE id = %s", user_id)中,user_id必须是元组或列表(如(user_id,)),漏掉逗号会变成整数传参,直接报错 - Java 的
Statement.executeQuery(sql)是裸拼接,必须用PreparedStatement+setString()等绑定方法
哪些地方必须参数化,却常被忽略
开发者容易只给 WHERE 条件加参数,但以下位置同样危险,且无法用过滤补救:
-
IN子句:不能写成"id IN (" + ids.join(',') + ")",需用动态占位符或 ORM 的安全批量方法 -
LIMIT和OFFSET:数值型参数必须走setInt()或对应类型绑定,不能拼进字符串 - 排序字段(
ORDER BY)和方向(ASC/DESC):这些是语法关键字,只能通过白名单校验,不能参数化——这是参数化本身的边界,不是缺陷 - 表名、列名、函数名:同上,属于 SQL 结构的一部分,必须严格白名单控制,比如
if sort_field not in ['created_at', 'score']: raise ValueError
最易被忽略的其实是动态 SQL 的“半参数化”场景:比如用字符串拼出 "SELECT * FROM " + table_name + " WHERE id = ?",前半截表名没校验,后半截参数再安全也没用。参数化只保数据,不保结构——这点一旦混淆,修复就形同虚设。

















