PDO防SQL注入需关闭模拟预处理、用占位符绑定用户数据、DSN声明charset=utf8mb4、LIKE通配符PHP层拼接、结构化参数白名单校验。

用PDO防止SQL注入,关键不是“加什么标签”,而是正确使用预处理语句 + 关键配置 + 严格绑定方式。所谓“PHP标签”其实是误解——真正起作用的是PDO的prepare()、execute()等方法调用,以及连接时的几项硬性设置。
下面说清楚必须做的几件事,不绕弯:
必须显式关闭模拟预处理
PDO默认开启ATTR_EMULATE_PREPARES = true,这意味着它在PHP端“假装”预处理,实际还是字符串拼接,完全不防注入。
必须在创建PDO实例时关掉:
- ✅ 正确写法:
$pdo = new PDO( "mysql:host=localhost;dbname=test;charset=utf8mb4", $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false // 这一行不能少 ] );
所有用户数据必须走占位符,禁止拼接
哪怕只拼一个变量,整个防护就失效。
- ❌ 危险(哪怕用了prepare):
$username = $_POST['name']; $stmt = $pdo->prepare("SELECT * FROM users WHERE name = '$username'"); // 字符串里已拼入变量! - ✅ 安全(命名占位符更清晰):
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name AND status = ?"); $stmt->execute(['name' => $_POST['name'], (int)$_POST['status']]);
字符集必须在DSN中声明
宽字节注入可绕过部分过滤,charset=utf8mb4要写进连接字符串,不能靠后续set names补救。
立即学习“PHP免费学习笔记(深入)”;
- ✅ 正确:
"mysql:host=localhost;dbname=test;charset=utf8mb4" - ❌ 不足:只写
"mysql:host=localhost;dbname=test",再单独执行SET NAMES utf8mb4
LIKE查询的通配符要在PHP层加,不能塞进占位符右边
占位符只传值,不参与SQL语法解析。所以%xxx%这种必须由PHP拼好再传入:
$keyword = "%{$_POST['keyword']}%"; // 在PHP里加%
$stmt = $pdo->prepare("SELECT * FROM articles WHERE title LIKE ?");
$stmt->execute([$keyword]);表名、列名、排序方向不能用占位符
预处理只保护值(value),不保护结构(如ORDER BY ?是语法错误)。这类动态片段必须白名单校验:
$allowed_sort = ['id', 'name', 'created_at']; $sort = in_array($_GET['sort'], $allowed_sort) ? $_GET['sort'] : 'id'; $direction = in_array($_GET['dir'], ['ASC', 'DESC']) ? $_GET['dir'] : 'ASC'; $sql = "SELECT * FROM users ORDER BY $sort $direction"; // 白名单后才可拼 $stmt = $pdo->prepare($sql); $stmt->execute();
不复杂但容易忽略。



















