跳转到主内容
极星编程网:以代码为星,赴技术山海!

PHP怎么使用OWASP Top 10防护_PHP十大漏洞防范指南【指南】

唯一可靠防SQL注入的方法是使用PDO::prepare()或mysqli_prepare()预处理语句,因其将SQL结构与数据彻底分离;禁用模拟预处理、动态标识符须白名单校验,addslashes()和mysql_real_escape_string()等函数已废弃且不安全。
PDO
和
mysqli_prepare
是 PHP 防御 SQL 注入最可靠的方式,其他所谓“过滤函数”或“转义函数”在复杂场景下极易失效。 用
PDO::prepare()
替代
mysql_real_escape_string()
老项目里常见
mysql_real_escape_string()
,但它只对单字节字符集有效,且无法防御宽字节注入、多语句注入等变种。现代 PHP(7.4+)已彻底移除该函数。 正确做法是统一使用
PDO
并开启
PDO::ATTR_EMULATE_PREPARES = false
:
$pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ]); $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ? AND status = ?"); $stmt->execute([$_GET['id'], 'active']);
必须关闭模拟预处理(
PDO::ATTR_EMULATE_PREPARES = false
),否则 PHP 会在客户端拼接 SQL,失去防护意义 占位符只能用于数据值,不能用于表名、列名、排序方向(
ORDER BY ?
是非法的) 如果真要动态列名,只能白名单校验:
in_array($_GET['sort'], ['name', 'created_at'], true)
mysqli
下必须用
mysqli_prepare()
,不是
mysqli_real_escape_string()
很多开发者误以为
mysqli_real_escape_string()
能替代预处理——它不能。该函数只是做字符串转义,仍可能被绕过(如 GBK 宽字节漏洞、JSON 字段内嵌 SQL 等)。 立即学习 “ PHP免费学习笔记(深入) ”; PHP 8.5.5 PHP 8.5.5 是 PHP 8.5 分支的维护更新版本。该版本延续了“小步快跑”的迭代逻辑,通过深度错误修复、底层性能微调以及安全加固,旨在为开发者提供一个更健壮、更高效的运行环境。该版本严格遵守语义化版本规范,不包含破坏性变更。 下载 正确写法:
$mysqli = new mysqli($host, $user, $pass, $db); $stmt = $mysqli->prepare("INSERT INTO logs (ip, action) VALUES (?, ?)"); $stmt->bind_param("ss", $_SERVER['REMOTE_ADDR'], $_POST['action']); $stmt->execute();
bind_param()
的第一个参数是类型字符串:
s
(string)、
i
(integer)、
d
(double)、
b
(blob) 不要用
mysqli_query($mysqli, "INSERT ... '".mysqli_real_escape_string($mysqli, $x)."'")
—— 这和拼接字符串没本质区别 注意:
mysqli_prepare()
不支持多语句(
INSERT; SELECT
),这是好事,反而堵住了一类攻击面 别在应用层“自己造轮子”过滤输入 OWASP Top 10 2025 明确指出,“输入验证”只是辅助手段,不能替代参数化查询。试图用正则过滤
'
、
;
、
--
、
/*
等符号,既不全面也不可持续。 攻击者可绕过:用十六进制编码(
0x756e696f6e
)、Unicode 同形字、注释变形(
/!UNION/
)、或利用 JSON/NoSQL 特性发起非 SQL 类注入 业务字段本身就需要特殊字符(如用户昵称含单引号、地址含分号),粗暴过滤会破坏功能 真正该做的,是在输出到 HTML 时用
htmlspecialchars()
,在输出到 JS 上下文时用
json_encode()
,在输出到 URL 时用
urlencode()
—— 场景决定编码方式,不是统一“过滤” 为什么你写了
prepare()
还被扫出 SQL 注入? 常见原因不是函数没用,而是调用姿势错了: 用了
PDO::query()
或
mysqli_query()
执行带变量的字符串 —— 即使前面声明了
prepare
对象也没用 把用户输入拼进了 SQL 模板里:
"SELECT * FROM {$table} WHERE ..."
—— 表名不可能参数化,必须白名单限制 在 ORM 中混用原生查询(如 Laravel 的
DB::select(DB::raw("..."))
),绕过了 ORM 的参数绑定机制 错误地认为“用了框架就安全”,但框架的
whereRaw()
、
orderByRaw()
等方法默认不绑定参数 参数化查询的防护边界非常清晰:只保数据值,不保语法结构。任何把用户输入塞进 SQL 字符串骨架里的操作,都在防线之外。

相关文章