PHP防SQL注入必须用PDO/MySQLi预处理语句为底线,配合白名单校验标识符、权限控制与错误脱敏;禁用addslashes等不安全函数,关闭PDO模拟预处理并显式指定utf8mb4字符集。

PHP 中 SQL 注入预防不是“加一层过滤就完事”,而是必须用预处理语句作为底线,再配合白名单校验、权限控制和错误脱敏才能真正落地有效。其他手段(如 addslashes()、mysql_real_escape_string())在 UTF-8 环境下仍可能被绕过,不建议单独使用。
必须用 PDO 或 MySQLi 的 prepare/execute,不能拼接 SQL 字符串
所有含用户输入的查询,只要出现在 WHERE、VALUES、SET 子句中,就必须走预处理流程。数据库驱动会在协议层将 SQL 结构与参数分离,连字符编码问题(比如宽字节注入)都由底层处理。
-
prepare()传入纯 SQL 模板,只含?或命名占位符(如:id),绝不能拼入变量 -
execute()或bindValue()传入实际值,PDO 会按类型自动转义;用PDO::PARAM_INT显式声明整型可防字符串伪装 - 不要混用:比如
"SELECT * FROM users WHERE id = " . (int)$_GET['id']看似安全,但一旦后续逻辑扩展为模糊搜索,就容易退化回拼接
表名、字段名、ORDER BY 方向等无法参数化的部分必须白名单校验
SQL 语法不允许对标识符(identifier)使用占位符,所以 $sort = $_GET['sort']; $sql = "ORDER BY $sort" 是高危写法,filter_var() 或 htmlspecialchars() 完全无效。
- 定义允许字段列表:
$allowed_columns = ['title', 'created_at', 'status'],用in_array($sort, $allowed_columns, true)严格比对 - 排序方向只接受
ASC/DESC,强制大写后校验:$order = strtoupper($_GET['order'] ?? 'ASC'); if (!in_array($order, ['ASC', 'DESC'])) die('Invalid order'); - 表名若需动态(如分表场景),必须从固定前缀 + 数字编号中生成,禁止任何用户可控字符串参与构造
PDO 连接必须关闭模拟预处理并启用异常模式
默认情况下,PDO 可能启用 PDO::ATTR_EMULATE_PREPARES = true,此时它只是在 PHP 层做字符串替换,不经过 MySQL 服务端预编译,等于形同虚设。
立即学习“PHP免费学习笔记(深入)”;
- 连接后立即设置:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false) - 同时开启异常模式:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION),避免query()失败静默返回false - 字符集必须显式指定为
utf8mb4(而非utf8),防止四字节 emoji 引发的边界解析错乱
输入验证要早、要严、要带范围,别等进 SQL 前才处理
验证不是为了“让数据看起来像合法输入”,而是提前拦截非法结构,减少后续处理负担。它和预处理不互斥,而是前后两道关卡。
- 整数 ID:
filter_var($_GET['id'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 1000000]]),返回false就直接拒绝 - 邮箱:
filter_var($_POST['email'], FILTER_VALIDATE_EMAIL),比正则更可靠且兼容 RFC 标准 - 自由文本长度限制:
mb_strlen($_POST['content']) > 2000就截断或报错,避免超长 payload 混淆日志或触发 WAF 误判 - 不要用
is_numeric()验证数字——它会放行"1e5"、"0x1F"这类非整型表达式
最容易被忽略的是:预处理只保数据安全,不保逻辑安全。比如一个用户能通过 id=123 查到本不该看的订单,那是越权问题,不是 SQL 注入。防护链条里,参数化是基础,白名单是补丁,权限最小化和业务校验才是兜底。



















