PDO防注入核心是全程使用预处理语句与参数绑定,禁用拼接;表名、字段名等无法参数化部分须白名单校验;配置ATTR_EMULATE_PREPARES=false、ERRMODE=EXCEPTION及utf8mb4字符集。

使用 PDO 防止 SQL 注入,核心在于绝不拼接用户输入到 SQL 字符串中,全程依赖预处理语句(Prepared Statements)与参数绑定。只要正确使用 prepare() + execute() 或 bindValue()/bindParam(),PDO 会自动对参数做类型安全的转义和隔离,从根本上阻断注入路径。
始终用预处理语句执行动态查询
所有含用户输入的查询(如 WHERE 条件、INSERT 值、ORDER BY 字段名除外)都必须走预处理流程。即使变量看起来“安全”,也不应例外。
- ✅ 正确:用占位符(
?或命名参数:name),再绑定值 - ❌ 错误:用字符串拼接或
sprintf插入变量
示例:
```php// ✅ 安全:位置占位符
$stmt = $pdo->prepare("SELECT * FROM users WHERE status = ? AND age > ?");
$stmt->execute(['active', 18]);
// ✅ 安全:命名参数
$stmt = $pdo->prepare("SELECT * FROM posts WHERE author_id = :uid AND category = :cat");
$stmt->bindValue(':uid', $_GET['id'], PDO::PARAM_INT);
$stmt->bindValue(':cat', $_GET['category'], PDO::PARAM_STR);
$stmt->execute();
```
慎处理无法参数化的部分(如表名、字段名、排序方向)
SQL 语法规定,表名、列名、ORDER BY 子句中的字段或 ASC/DESC 不允许用参数占位符。此时必须白名单校验,不可依赖过滤或转义。
立即学习“PHP免费学习笔记(深入)”;
- 定义允许的字段列表(如
['id', 'title', 'created_at']),用in_array()严格比对 - 排序方向只接受
'ASC'或'DESC',强制转换为大写并校验 - 禁止将任何用户输入直接代入 SQL 字符串拼接
示例:
```php$allowed_columns = ['name', 'email', 'created_at'];
$sort_column = $_GET['sort'] ?? 'created_at';
$sort_order = strtoupper($_GET['order'] ?? 'ASC');
if (!in_array($sort_column, $allowed_columns)) {
throw new InvalidArgumentException('Invalid sort column');
}
if (!in_array($sort_order, ['ASC', 'DESC'])) {
throw new InvalidArgumentException('Invalid sort order');
}
$sql = "SELECT * FROM users ORDER BY {$sort_column} {$sort_order}";
$stmt = $pdo->query($sql); // 注意:这里没参数,但字段和方向已白名单控制
```
设置正确的 PDO 属性与错误模式
默认情况下 PDO 可能静默失败或返回不安全的错误信息。需主动配置以增强安全性与可观测性。
- 禁用模拟预处理:
PDO::ATTR_EMULATE_PREPARES => false,确保数据库原生支持预处理,避免绕过 - 开启异常模式:
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,便于捕获和处理错误,防止敏感信息泄露 - 设置默认字符集(如
utf8mb4),避免因编码不一致导致的绕过场景
连接示例:
```php$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
];
$pdo = new PDO($dsn, $user, $pass, $options);
```
其他关键细节
补充几个容易被忽略但影响安全性的点:
- 对整数型参数,显式使用
PDO::PARAM_INT绑定,避免字符串被当作表达式解析(如'1 OR 1=1') - 批量插入时,用单条预处理语句循环
execute(),不要拼接多值VALUES (?,?),(?,?)后再绑定(除非确认所有参数类型一致且可控) - 避免在
execute()数组中混用未过滤的原始输入;优先用bindValue()分步控制类型 - 关闭
PDO::ATTR_STRINGIFY_FETCHES(保持数值类型原样),减少类型隐式转换风险











