字符串拼接sql是高危操作,因将用户输入直接嵌入sql语句导致数据与代码混淆;必须使用真实预处理(禁用pdo模拟模式)、严禁参数化表名/字段名,此类场景须用白名单校验。

为什么字符串拼接SQL在PHP里是高危操作
直接把用户输入塞进mysql_query()或mysqli_query()的SQL字符串里,等于把数据库钥匙交到对方手上。哪怕加了addslashes()或htmlspecialchars(),也挡不住绕过型注入(比如宽字节、十六进制编码、注释符截断)。MySQLi和PDO原生支持预处理,但前提是**你得用它**,而不是只把它当个“更现代的连接方式”。
必须用预处理语句,且参数不能出现在SQL模板里
预处理的安全性来自SQL结构与数据的彻底分离。只要参数进了bind_param()或execute()的数组,就再不会被解析为SQL语法。
-
mysqli写法示例:
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ? AND status = ?");
$stmt->bind_param("is", $id, $status);
$stmt->execute();
-
PDO写法示例:
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(['email' => $user_input]);
bind_param()或execute()的数组,就再不会被解析为SQL语法。
-
mysqli写法示例:$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ? AND status = ?"); $stmt->bind_param("is", $id, $status); $stmt->execute(); -
PDO写法示例:$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email"); $stmt->execute(['email' => $user_input]);
常见错误包括:
- 用
sprintf()或str_replace()“模拟”预处理(依然属于拼接) - 把表名、字段名、排序方向(如
ORDER BY ?)也用占位符(不合法,会报错) - 忘记检查
prepare()返回值,导致后续bind_param()调用失败却没察觉
动态表名/字段名怎么办?白名单校验是唯一可行方案
预处理无法绑定标识符(如table_name、ORDER BY column),硬塞进去会触发语法错误或被忽略。
-
只能从预设列表中匹配:
$allowed_tables = ['users', 'posts', 'comments'];
if (!in_array($user_table, $allowed_tables, true)) {
throw new InvalidArgumentException('Invalid table name');
}
$sql = "SELECT * FROM `{$user_table}` WHERE active = 1";
-
排序字段同理:
$sort_fields = ['created_at', 'title', 'score'];
$sort = in_array($_GET['sort'] ?? '', $sort_fields) ? $_GET['sort'] : 'created_at';
$sql = "SELECT * FROM items ORDER BY `$sort` DESC";
只能从预设列表中匹配:
$allowed_tables = ['users', 'posts', 'comments'];
if (!in_array($user_table, $allowed_tables, true)) {
throw new InvalidArgumentException('Invalid table name');
}
$sql = "SELECT * FROM `{$user_table}` WHERE active = 1";
排序字段同理:
$sort_fields = ['created_at', 'title', 'score']; $sort = in_array($_GET['sort'] ?? '', $sort_fields) ? $_GET['sort'] : 'created_at'; $sql = "SELECT * FROM items ORDER BY `$sort` DESC";
注意:反引号包裹、严格in_array(..., true)、拒绝空值或非法字符,三者缺一不可。
别信“我只用PDO,所以安全”——PDO默认不启用真实预处理
PDO有个隐藏陷阱:PDO::ATTR_EMULATE_PREPARES默认为true,意味着它只是在PHP层做字符串替换,**不是真正的预处理**,仍可能被绕过。
-
必须显式关闭:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
验证是否生效:查PDO::getAvailableDrivers()确认MySQL驱动可用,并在执行后检查$stmt->queryString是否含原始占位符(而非已替换内容)。
必须显式关闭:
$pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);
验证是否生效:查PDO::getAvailableDrivers()确认MySQL驱动可用,并在执行后检查$stmt->queryString是否含原始占位符(而非已替换内容)。
真正危险的不是不会写预处理,而是写了却因配置或认知偏差,让它形同虚设。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











