ThinkPHP SQL注入漏洞本质是用户输入绕过参数绑定机制直接拼入SQL,关键在于开发者触发字符串拼接where、滥用raw/exp、忽略数组过滤或误用原生query等危险路径。

ThinkPHP 的 SQL 注入漏洞本质是用户输入绕过了参数绑定机制,直接拼入 SQL 字符串中执行。关键不在框架有没有防御能力,而在于开发者是否触发了“绕过路径”——比如字符串拼接 where、滥用 raw/exp、忽略数组参数过滤、或误用原生 query。
一、典型注入原理:哪里被绕过了
ThinkPHP 默认对数组格式的 where(['id' => $id]) 或闭包形式自动启用 PDO 参数绑定,但以下写法会完全跳过绑定:
-
字符串拼接 where:如
where("id = " . input('id'))—— 变量直插,无任何转义 -
可控 order/group by:如
order(input('sort'))—— 框架不校验字段名,攻击者传id desc, (select version())即可注入 -
ThinkPHP 3.2.3 数组参数解析缺陷:当调用
Model::find(['table' => 'user UNION SELECT 1,2,3--'])时,_parseOptions()直接将恶意字符串写入 SQL 模板,parseKey()不做任何过滤,最终生成SELECT * FROM user UNION SELECT 1,2,3-- -
开启 PDO 模拟预处理:配置中
PDO::ATTR_EMULATE_PREPARES => true会使预处理退化为字符串拼接,尤其在 MySQL 5.6 或 Docker 环境下极易触发
二、防御代码:四条硬性规则
不是加个过滤函数就行,必须从写法源头堵死。以下是生产环境验证有效的防御代码示例:
✅ 正确使用数组/闭包 where(强制绑定)
立即学习“PHP免费学习笔记(深入)”;
// ✅ 安全:自动绑定
$user = Db::name('user')->where(['id' => input('id', 0, 'intval'), 'status' => 1])->find();
<p>// ✅ 安全:闭包内仍走绑定
Db::name('user')->where(function ($query) {
$query->where('name', 'like', '%' . input('kw') . '%')
->whereOr('email', input('kw'));
})->select();
✅ 原生 SQL 必须显式绑定(禁用裸字符串)
// ✅ 安全:位置占位符 + 参数数组
Db::query("SELECT * FROM user WHERE id IN (?) AND status = ?", [$idList, $status]);
<p>// ✅ 安全:命名占位符(注意键名带冒号)
Db::query("SELECT * FROM user WHERE name = :name AND level > :level", [
':name' => input('name'),
':level' => (int)input('level', 1)
]);</p><p>// ❌ 危险:绝不能这样写
// Db::query("SELECT * FROM user WHERE id = " . input('id'));
✅ 关键配置关闭危险开关
// 数据库连接配置中(database.php 或 config/database.php)
return [
// ...
'params' => [
\PDO::ATTR_EMULATE_PREPARES => false, // 必须设为 false
],
// ...
];
✅ 输入强校验 + 白名单兜底
// 字段名、排序方向、IN 子句等动态内容必须白名单控制
$sortField = input('sort', 'id');
$sortOrder = input('order', 'desc');
if (!in_array($sortField, ['id', 'username', 'create_time', 'status'])) {
$sortField = 'id';
}
if (!in_array($sortOrder, ['asc', 'desc'])) {
$sortOrder = 'desc';
}
$list = Db::name('user')->order($sortField . ' ' . $sortOrder)->select();
<p>// IN 查询:动态生成占位符,不拼数组
$ids = array_map('intval', input('ids/a', []));
if (!empty($ids)) {
$placeholders = str_repeat('?,', count($ids) - 1) . '?';
$users = Db::query("SELECT * FROM user WHERE id IN ($placeholders)", $ids);
}
三、必须禁用的危险操作
以下接口和写法在任何版本中都应全局禁止,它们等于主动打开注入大门:
-
raw() 和 exp() 方法:如
where(['status' => ['exp', "1=1 OR username='admin'"]])—— 完全绕过框架安全层 -
手动拼接 table 或 alias:如
table(input('table_name'))或alias(input('alias')) -
未过滤的 $_GET/$_POST 直接进查询:哪怕只取一个 ID,也必须经
input('id', 0, 'intval')或类型强转 - ThinkPHP 3.2.3 的 find($array)、select($array):该版本已知存在数组参数解析漏洞,升级或重写逻辑
防御不是靠“多加一层过滤”,而是让危险路径根本不可达。只要坚持数组/闭包写法、显式绑定原生 SQL、关闭模拟预处理、所有动态值走白名单,SQL 注入就无法落地。



















