最彻底、最推荐的修复方式是全部使用PDO预处理语句替代SQL拼接。必须用prepare()和execute()分离执行,禁用模拟预处理,所有变量(含数字、布尔、日期)均须绑定传参,严禁拼接表名、字段名或ORDER BY子句。

直接用PDO预处理语句替换所有拼接SQL
这是最彻底、最推荐的修复方式。只要把原来用$sql = "SELECT * FROM users WHERE id = " . $_GET['id']这类拼接写法,全部换成PDO预处理,就能从根源上切断SQL注入路径。
关键不是“加一层过滤”,而是让数据库引擎根本不会把用户输入当SQL代码解析。哪怕输入是admin' OR '1'='1,绑定后也只当作字符串值处理。
- 必须使用
prepare()和execute()两步分离,不能写成$pdo->query("SELECT ... WHERE id = " . $_GET['id']) - 占位符用
?或命名参数(如:id),不要拼进字符串里 - 所有变量——包括数字、布尔、日期——都必须走
execute()传参,不能用(int)强制转换后拼接 - 注意PDO默认不开启
PDO::ATTR_EMULATE_PREPARES = false,要显式关闭模拟预处理,否则MySQLi驱动下可能退化为字符串拼接
mysqli_real_escape_string()只能临时救急,别当主力
mysqli_real_escape_string()看起来像补丁,但实际风险很高:它依赖当前连接的字符集,GBK、BIG5等宽字节编码下容易被绕过;且只对字符串有效,对ORDER BY、INSERT INTO table_name这种结构级注入完全无效。
更麻烦的是,它要求你先建立数据库连接才能调用——这意味着在连接失败时,这个函数会报错甚至导致白屏,反而暴露了底层结构。
立即学习“PHP免费学习笔记(深入)”;
- 仅限于无法立刻重构的老代码中做过渡使用
- 必须配合
set_charset()确保连接字符集与页面一致(如UTF8MB4) - 绝对不能用于构造表名、字段名、
ORDER BY子句等非数据位置 - 如果项目已用PDO,就别混用mysqli函数,维护成本陡增
数字型参数必须用类型强转,但仅限明确场景
像分页参数$_GET['page']、ID查询$_GET['id']这类纯数字输入,用(int)或filter_var($id, FILTER_VALIDATE_INT)是安全且高效的。数据库层不会解析整数为代码,天然免疫注入。
但这个方法有硬性边界:一旦字段允许字母、下划线、短横线(比如订单号ORD-2026-ABC),就不能再用强转,否则会截断或归零,直接破坏业务逻辑。
-
(int)会把"123abc"变成123,看似安全实则丢失数据 -
filter_var($val, FILTER_SANITIZE_NUMBER_INT)会删掉所有非数字字符,同样不可逆 - 只要字段定义允许非数字字符,就必须回归预处理,没有例外
别信addslashes()和自定义黑名单
addslashes()在PHP 8+中已基本失效——它不感知连接字符集,也不处理十六进制编码、Unicode代理对等绕过手段;而手写黑名单(比如过滤union、select)更是徒劳,大小写变形、注释分隔、URL编码都能轻松绕过。
真实项目里见过太多“加了黑名单后上线一周就被打穿”的案例,问题不在规则不够多,而在于黑名单本质是防御者永远落后于攻击者的设计缺陷。
-
addslashes()只对ASCII单引号/双引号有效,对0x27(十六进制单引号)完全无感 - 黑名单匹配用
str_replace()或preg_replace(),若未加i修饰符,SeLeCt就逃逸成功 - 任何试图靠字符串清洗代替语法隔离的方案,都只是给漏洞贴创可贴
预处理不是“多写几行代码”的负担,而是把SQL结构和数据彻底解耦的强制契约。最容易被忽略的点是:哪怕只有一处漏网的拼接,整个防护体系就形同虚设——攻击者不需要攻破全部,只要找到一个没走prepare()的地方,就能撬开整张表。



















