如何利用mysqli_stmt_prepare防止PHP中SQL注入?
- 内容介绍
- 文章标签
- 相关推荐
本文共计991个文字,预计阅读时间需要4分钟。
直接使用 `mysqli_prepare()` 和 `mysqli_stmt_bind_param()` 是一种依赖原型的注入防御路径,它主要依靠参数绑定来防止注入攻击。其他任何字符字符串拼接、转换或过滤都不算真正的防御——它们只是延迟漏洞暴露的时间而已。
为什么 mysqli_query() 拼字符串一定会出事
因为数据库引擎看到的是完整语句,用户输入一旦没被隔离,就自动获得“代码身份”。比如 id = $_GET['id'],传入 1 OR 1=1,拼出来就是 SELECT * FROM user WHERE id = 1 OR 1=1,全表返回。
常见错误包括:
- 只对字符串字段用
mysqli_real_escape_string(),但数字字段不加引号,转义完全无效 - 忘了调用
mysqli_set_charset($conn, 'utf8mb4'),宽字节注入可绕过转义 - 把变量拼进 SQL 字符串里再传给
mysqli_prepare(),比如"WHERE name = '{$_POST['name']}'",prepare 根本没起作用
mysqli_prepare() 的正确调用链
必须按顺序走完三步:准备 → 绑定 → 执行。缺一不可,且绑定必须在执行前完成。
立即学习“PHP免费学习笔记(深入)”;
关键点:
-
mysqli_prepare()返回的是mysqli_stmt对象,不是结果集 -
mysqli_stmt_bind_param()第一个参数是类型字符串,如"si",长度和后续变量个数必须严格一致 - 绑定的变量必须是真实存在的变量(不能是表达式或函数返回值),且需提前赋值
- 占位符
?不能加引号,也不能用于字段名、表名、ORDER BY或GROUP BY后的内容
示例:
$stmt = mysqli_prepare($conn, "SELECT * FROM users WHERE email = ? AND status = ?"); mysqli_stmt_bind_param($stmt, "si", $email, $status); $email = $_POST['email'] ?? ''; $status = (int)($_POST['status'] ?? 1); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt);
类型标识写错会怎样
"s"(字符串)、"i"(整数)、"d"(浮点)、"b"(BLOB)必须和变量实际类型匹配。错一个就会触发 Number of variables doesn't match number of parameters in prepared statement 错误,或者更危险的隐式截断/转换。
典型陷阱:
- 用
"s"绑定一个未初始化的空变量,可能被当成空字符串插入,掩盖逻辑缺陷 - 用
"i"绑定含非数字字符的字符串(如"123abc"),PHP 会静默转成123,丢失校验意图 - 整数字段却用
"s",虽能执行,但可能触发 MySQL 的隐式类型转换,某些边界 case 下仍可绕过
动态表名或排序方向怎么办
预处理语句不支持绑定表名、字段名、ASC/DESC。这时候不能妥协,必须用白名单硬校验。
例如:
- 表名只能从
['users', 'posts', 'comments']中取 - 排序字段限定为
['created_at', 'title', 'score'] -
ORDER BY方向只接受$_GET['sort'] === 'DESC' ? 'DESC' : 'ASC'
拼接前先做 in_array() 判断,不匹配就拒绝请求。别试图用 addslashes() 或正则“清理”字段名——那等于没防。
最常被忽略的一点:预处理防的是“值”,不是“结构”。只要 SQL 模板本身混入了未校验的用户输入,哪怕用了 prepare(),照样崩。安全不在函数名里,而在你控制数据流向的每一步是否真正隔离。
本文共计991个文字,预计阅读时间需要4分钟。
直接使用 `mysqli_prepare()` 和 `mysqli_stmt_bind_param()` 是一种依赖原型的注入防御路径,它主要依靠参数绑定来防止注入攻击。其他任何字符字符串拼接、转换或过滤都不算真正的防御——它们只是延迟漏洞暴露的时间而已。
为什么 mysqli_query() 拼字符串一定会出事
因为数据库引擎看到的是完整语句,用户输入一旦没被隔离,就自动获得“代码身份”。比如 id = $_GET['id'],传入 1 OR 1=1,拼出来就是 SELECT * FROM user WHERE id = 1 OR 1=1,全表返回。
常见错误包括:
- 只对字符串字段用
mysqli_real_escape_string(),但数字字段不加引号,转义完全无效 - 忘了调用
mysqli_set_charset($conn, 'utf8mb4'),宽字节注入可绕过转义 - 把变量拼进 SQL 字符串里再传给
mysqli_prepare(),比如"WHERE name = '{$_POST['name']}'",prepare 根本没起作用
mysqli_prepare() 的正确调用链
必须按顺序走完三步:准备 → 绑定 → 执行。缺一不可,且绑定必须在执行前完成。
立即学习“PHP免费学习笔记(深入)”;
关键点:
-
mysqli_prepare()返回的是mysqli_stmt对象,不是结果集 -
mysqli_stmt_bind_param()第一个参数是类型字符串,如"si",长度和后续变量个数必须严格一致 - 绑定的变量必须是真实存在的变量(不能是表达式或函数返回值),且需提前赋值
- 占位符
?不能加引号,也不能用于字段名、表名、ORDER BY或GROUP BY后的内容
示例:
$stmt = mysqli_prepare($conn, "SELECT * FROM users WHERE email = ? AND status = ?"); mysqli_stmt_bind_param($stmt, "si", $email, $status); $email = $_POST['email'] ?? ''; $status = (int)($_POST['status'] ?? 1); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt);
类型标识写错会怎样
"s"(字符串)、"i"(整数)、"d"(浮点)、"b"(BLOB)必须和变量实际类型匹配。错一个就会触发 Number of variables doesn't match number of parameters in prepared statement 错误,或者更危险的隐式截断/转换。
典型陷阱:
- 用
"s"绑定一个未初始化的空变量,可能被当成空字符串插入,掩盖逻辑缺陷 - 用
"i"绑定含非数字字符的字符串(如"123abc"),PHP 会静默转成123,丢失校验意图 - 整数字段却用
"s",虽能执行,但可能触发 MySQL 的隐式类型转换,某些边界 case 下仍可绕过
动态表名或排序方向怎么办
预处理语句不支持绑定表名、字段名、ASC/DESC。这时候不能妥协,必须用白名单硬校验。
例如:
- 表名只能从
['users', 'posts', 'comments']中取 - 排序字段限定为
['created_at', 'title', 'score'] -
ORDER BY方向只接受$_GET['sort'] === 'DESC' ? 'DESC' : 'ASC'
拼接前先做 in_array() 判断,不匹配就拒绝请求。别试图用 addslashes() 或正则“清理”字段名——那等于没防。
最常被忽略的一点:预处理防的是“值”,不是“结构”。只要 SQL 模板本身混入了未校验的用户输入,哪怕用了 prepare(),照样崩。安全不在函数名里,而在你控制数据流向的每一步是否真正隔离。

