FlawOpen

flawopen.com/PHP 中的 SQL Injection/mysqli 与 PDO

MySQLi 与 PDO:安全使用预处理语句

参考页面
简答

预处理语句固定 SQL 指令,并单独发送输入值,就像填写带标签的空格。PDO 和 MySQLi 都能做到。安全性取决于是否正确绑定值,而非库的名称。

同一查询在两种 API 中的安全写法

不安全 — SQL 拼入请求数据
$userId = $_GET['id'];
$sql = "SELECT id, email FROM users WHERE id = " . $userId;

// Both calls execute the already-built SQL string:
$rows = $mysqli->query($sql);
$rows = $pdo->query($sql);
安全 — 两种 API 均绑定值
// PDO
$stmt = $pdo->prepare(
    "SELECT id, email FROM users WHERE id = :id"
);
$stmt->execute(["id" => $userId]);

// MySQLi
$stmt = $mysqli->prepare(
    "SELECT id, email FROM users WHERE id = ?"
);
$stmt->bind_param("i", $userId);
$stmt->execute();

数据库可移植性是 PDO 的主要区别

数据库可移植性是 PDO 的主要区别

PDO 为多个数据库驱动提供统一接口;MySQLi 面向 MySQL 和 MariaDB。PDO 本身不会让 SQL 自动可移植:方言、驱动行为和功能仍有差异。

占位符写法有所不同

根据驱动,PDO 可使用 :id 命名占位符或 ? 位置占位符;不要混用。MySQLi 使用位置 ?。两者都绑定值,不能绑定表名、列名、关键字或排序方向。

PDO 预处理模式取决于驱动

PDO 驱动可能使用原生或模拟预处理。对 MySQL,PDO::ATTR_EMULATE_PREPARES 在支持时控制模拟;原生预处理不可用或失败时,驱动可能模拟或回退。应遵循驱动文档。此设置不会保护插值 SQL:无论模式如何,都应固定查询文本并绑定值。

两种库都适用的 SQL Injection 防护规则

使用 prepare() 并绑定每个不可信值。保持 SQL 语法固定;动态标识符应从严格的服务端映射中选择,或构造有限的查询变体。预处理语句不负责授权行访问;必要时按已认证用户限定查询。对于 LIKE,绑定可防止 SQL 语法注入,但若未转义为字面搜索,% 和 _ 仍是通配符。

如何选择 PDO 或 MySQLi

常见问题

PDO 命名占位符比 MySQLi 的问号更安全吗?

不能。只要查询经过预处理且值被绑定,两者都能安全绑定值。命名占位符可读性更好,但不能绑定 SQL 标识符,也不能替代授权。

应该始终禁用 PDO 模拟预处理吗?

不应一概而论。驱动支持和回退行为各异。查阅驱动文档和应用需求。任何模式下都要固定 SQL 结构并将值作为参数传入。

预处理语句能绑定表名或列名吗?

不能。绑定参数代表值,不能代表标识符或 SQL 关键字。应在服务端将客户端选项映射到有限的可信 SQL 片段。

参考资料

另请参阅 SQL 注入(CWE-89):原理与各编程语言的防护方法 — flawopen.com NHibernate 能防止 SQL 注入吗? MongoDB NoSQL 注入:安全查询过滤器 数字 ID 使用字符串拼接也会导致 SQL 注入吗? 什么是参数化查询? Prisma $queryRaw 能防止 SQL 注入吗? Rails where() 字符串插值安全吗?