必须关闭pdo模拟预处理(pdo::attr_emulate_prepares => false),否则prepare()形同虚设;真实预处理要求数据库服务端编译sql模板,参数作为独立数据传入,不参与语法解析,才能彻底防御sql注入。

必须关闭PDO模拟预处理
默认情况下,PDO会启用PDO::ATTR_EMULATE_PREPARES => true,这意味着它根本没把SQL发给MySQL做真实预处理,而是在PHP层把占位符替换成字符串再执行——和拼接没区别,宽字节、多语句、特殊编码都能绕过。
真实预处理要求数据库服务端编译SQL模板,参数作为独立数据传入,不参与语法解析。不关掉模拟,prepare()就形同虚设。
- 连接时必须显式设置:
new PDO($dsn, $user, $pass, [PDO::ATTR_EMULATE_PREPARES => false]) - 搭配
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,让错误不静默吞掉 - 字符集必须在DSN里声明:
mysql:host=localhost;dbname=test;charset=utf8mb4,否则mysqli_real_escape_string类漏洞可能复现
占位符只能用于值,不能用于表名或字段名
预处理语句只保护SQL中的“值”(WHERE条件、INSERT字段值等),对表名、列名、ORDER BY子句、LIMIT偏移量等动态结构无效。这些地方用占位符会直接报错或被忽略。
比如$stmt = $pdo->prepare("SELECT * FROM :table WHERE id = ?")是错的;ORDER BY name :sort也不行——冒号后面不能跟变量名。
- 表名/字段名/排序方向必须走白名单校验:
$table = in_array($_GET['table'], ['users', 'logs']) ? $_GET['table'] : 'users'; - LIMIT参数如果是用户控制的偏移量,也要强制转整型并范围检查:
$offset = max(0, min(1000, (int)$_GET['offset'])) - LIKE查询中通配符必须在PHP层加:
$search = '%' . $pdo->quote($_POST['q']) . '%',或更安全地用"%{$_POST['q']}%"再绑定,但需确保$_POST['q']已过滤非ASCII控制字符
绑定参数时类型声明不能省
虽然execute([$value])能自动推断类型,但在边界场景下容易出问题:比如数字字段传入空字符串'',可能被当作0插入;时间字段传入'now()'字符串,不会触发MySQL函数。
明确指定类型可堵住这类隐式转换漏洞,也避免因类型不匹配导致查询无结果却没报错。
- 用
bindValue()比execute()数组更可控:$stmt->bindValue(':id', $_GET['id'], PDO::PARAM_INT) - 字符串一律用
PDO::PARAM_STR,哪怕看起来像数字(如身份证号、订单号) - 布尔值注意MySQL存的是
TINYINT(1),应转为(int)$bool再绑定,或用PDO::PARAM_BOOL - NULL值要显式绑定:
$stmt->bindValue(':email', null, PDO::PARAM_NULL),不能传''或false
别信任何escape类函数替代预处理
mysqli_real_escape_string()或addslashes()只对单引号、反斜杠做转义,完全防不住数字型注入(如id=1 OR 1=1)、联合查询(UNION SELECT)、报错注入(extractvalue())等手法。它们还严重依赖字符集一致性,一旦连接未设utf8mb4,宽字节就能绕过。
更危险的是,开发者常误以为加了intval()就安全了,但只要SQL里还有字符串字段拼接,整个查询就仍可被突破。
- 所有外部输入——无论来自
$_GET、$_POST、$_COOKIE甚至文件读取——只要进SQL,就必须走占位符 - 框架里的查询构造器(如Laravel的
where()、ThinkPHP的where('id', $id))底层也是预处理,可信;但手写原生SQL时,绝不能出现"WHERE name = '" . $_POST['name'] . "'" - 临时调试用的
var_dump()或echo输出SQL字符串,务必删干净,否则可能暴露带敏感参数的原始SQL
真实预处理生效与否,不是看代码有没有prepare(),而是看MySQL服务端是否收到带问号的原始语句。最容易被忽略的,是那个默认开启的emulate_prepares开关——它藏在配置深处,不出错时根本不会提醒你。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











