pdo和mysqli的预处理语句是当前php防sql注入最可靠方式,需禁用模拟预处理、正确绑定参数类型,表名等须白名单校验,配合最小权限与错误信息隐藏。

PDO 和 mysqli 的预处理语句是当前 PHP 防 SQL 注入最可靠、最主流的方式,其他方法(如 addslashes()、mysql_real_escape_string())要么已废弃,要么存在宽字节、字符集错配等绕过风险,不建议单独使用。
用 PDO::prepare() + bindValue() 绑定参数才是正解
预处理语句把 SQL 结构和数据彻底分离:数据库先编译语句模板,再把用户输入作为纯数据传入,根本不会解析成 SQL 代码。哪怕你传入 ' OR 1=1 -- ,它也只会被当作一个字符串值匹配,不会改变查询逻辑。
关键点:
-
PDO必须禁用模拟预处理(PDO::ATTR_EMULATE_PREPARES => false),否则在某些 MySQL 配置下会退化为字符串拼接,失去防护能力 - 数字类型优先用
PDO::PARAM_INT,字符串用PDO::PARAM_STR,避免隐式类型转换漏洞 - 表名、字段名、
ORDER BY子句不能参数化,必须通过白名单校验(例如:in_array($sort, ['id', 'name', 'created_at']))
示例:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE status = ? AND age > ?');
$stmt->bindValue(1, $_GET['status'], PDO::PARAM_STR);
$stmt->bindValue(2, (int)$_GET['age'], PDO::PARAM_INT);
$stmt->execute();
mysqli 中必须用 bind_param(),别只调 real_escape_string()
mysqli_real_escape_string() 仅在特定字符集(如 utf8mb4)且连接层与客户端字符集严格一致时才安全;一旦出现 GBK 宽字节、连接未显式设置 charset,就可能被绕过。而 bind_param() 完全规避字符集问题。
注意:
- 参数类型字母必须准确:
i(int)、s(string)、d(double)、b(blob),类型错配可能导致绑定失败或静默截断 - 变量必须是引用(
&$var),直接传值会报mysqli_stmt::bind_param(): Number of variables doesn't match number of parameters - 不要混用:写了
bind_param()就别再对同一变量调real_escape_string(),徒增冗余且无额外收益
示例:
$mysqli = new mysqli($host, $user, $pass, $db);
$stmt = $mysqli->prepare('INSERT INTO logs (ip, action) VALUES (?, ?)');
$ip = $_SERVER['REMOTE_ADDR'];
$action = $_POST['action'] ?? '';
$stmt->bind_param('ss', $ip, $action); // 两个 s,对应两个 string 参数
$stmt->execute();
过滤函数不是防注入的主力,而是辅助手段
filter_var()、ctype_digit()、intval() 这类函数适合做「前置校验」,但不能替代预处理。它们的作用是快速拒绝非法输入,减少无效请求进入数据库层。
常见误用场景:
- 对邮箱用
filter_var($email, FILTER_VALIDATE_EMAIL)后直接拼 SQL —— 仍可能被绕过,比如admin@example.com' --符合邮箱格式但含恶意字符 - 用
htmlspecialchars()防 SQL 注入 —— 它只转义 HTML 实体,对数据库毫无作用,属于 XSS 防护范畴 - 依赖
addslashes()—— 它不感知字符集,在 GBK 环境下易触发宽字节注入,PHP 8.4 已标记为废弃
正确做法:校验通过后,依然走 PDO 或 mysqli 的参数绑定流程。
权限最小化 + 错误信息不暴露是最后一道防线
即使代码层完全没出错,数据库账户权限过大也会放大攻击后果。比如一个只读接口,数据库账号却有 DROP TABLE 权限,一旦出现未知漏洞,后果严重。
必须做到:
- 为每个应用创建独立数据库用户,仅授予
SELECT/INSERT/UPDATE等必要权限,禁用FILE、LOAD DATA、EXECUTE - 生产环境关闭
display_errors,错误日志写文件而非输出到页面,避免泄露表结构、字段名、路径等敏感信息 - 不要在异常消息里拼接原始 SQL ——
echo "Query failed: $sql"是高危操作
真正容易被忽略的是:很多团队花大量时间写复杂的输入过滤逻辑,却忘了检查数据库连接用的是 root 还是专用低权账号 —— 这个配置项往往藏在 config.php 或 .env 里,一次疏忽就让所有防护形同虚设。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











