唯一可靠的方式是彻底放弃字符串拼接,改用数据库驱动原生支持的参数化查询(prepared statements),必须禁用模拟预处理、严格校验动态表名列名白名单,并确保类型绑定正确。

唯一可靠的方式是彻底放弃字符串拼接,改用数据库驱动原生支持的参数化查询(Prepared Statements)。任何过滤、转义、正则黑名单,都挡不住真实攻击。
为什么 mysql_real_escape_string 和 addslashes 不能防注入
它们只是对单引号加反斜杠,但攻击者可以绕过:用十六进制编码(0x61646d696e)、用注释符 /**/ 拆开关键字、用双写 seleselectct 触发解析器误判。更关键的是,这类函数对数字型参数完全无效——输入 1 OR 1=1 不含单引号,照样生效。
实际场景中,只要看到代码里出现 "WHERE id = " + user_id 或 'username=' + name,就已处于高危状态。
PDO::prepare() 和 mysqli_prepare() 的正确用法
必须确保底层真正启用预编译,而不是模拟预处理(emulated prepares)。PHP 默认开启模拟模式,它会把参数拼回 SQL 字符串再发给 MySQL,等于白做。
- 显式关闭模拟:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false) - 确认连接使用的是原生预处理:执行
SELECT @@session.prepared_stmt_count,有值增长说明生效 - 参数只能用于值(
WHERE name = ?),不能用于表名、列名、排序字段——这些必须走白名单校验
Python cursor.execute() 常见错用
很多人以为传入元组就安全了,但错在用错了占位符或类型不匹配:
- 用
%s占位符时,必须传元组或列表:cursor.execute("SELECT * FROM t WHERE id = %s", (user_id,))—— 注意末尾逗号,否则(user_id)是 int 不是 tuple - 绝不能用 f-string 或
.format()拼接:f"WHERE id = {user_id}"或"WHERE id = {}".format(user_id)都是高危 - PostgreSQL 的
psycopg2用%s,不是?;SQLite 两者都支持,但保持统一更稳妥
动态字段(表名、ORDER BY)怎么处理
这些无法参数化,硬塞 ? 会报错,然后有人退回去拼字符串——这是最危险的临界点。
可行方案只有白名单校验:
- 定义允许的列名集合:
allowed_sort_fields = {"created_at", "name", "status"} - 校验输入是否严格属于该集合:
if sort_field not in allowed_sort_fields: raise ValueError("Invalid sort field") - 表名同理,不能靠“过滤掉
;”或“只允许字母”,而要穷举合法值
真正的难点不在语法,而在业务变化时忘记同步更新白名单——这比拼接字符串更隐蔽,也更难审计。











