Hyperf 的 SQL 注入防护依赖正确使用参数绑定机制。Db::select() 和 Db::update() 必须传第二个参数数组;禁用字符串拼接和误用 Db::raw();动态字段需白名单校验;事务内原始 SQL 同样要绑定;上线前确认 PDO::ATTR_EMULATE_PREPARES 为 false。

Hyperf 的 SQL 注入防护不靠框架自动“兜底”,而靠你是否真正用对了 Db::select()、Db::update() 等方法的参数绑定机制。拼字符串、绕过第二个参数、误用 Db::raw(),都会让预处理形同虚设。
Db::select() 和 Db::update() 必须传第二个参数数组
Hyperf 的查询构造器底层走的是 PDO 预处理,但前提是变量必须作为独立参数传入,而不是塞进 SQL 字符串里。
- 错误写法:
Db::select("SELECT * FROM user WHERE id = {$id} AND name = '{$name}'")——完全跳过绑定,$id和$name直接拼接,SQL 注入大门敞开 - 正确写法(问号占位):
Db::select("SELECT * FROM user WHERE id = ? AND name = ?", [$id, $name]) - 更推荐写法(命名占位):
Db::select("SELECT * FROM user WHERE id = :id AND name = :name", ['id' => $id, 'name' => $name]),重复使用同一变量时不易出错,也方便调试
Db::raw() 不是变量容器,只用于固定表达式
Db::raw() 的作用是告诉查询构造器:“这段内容别转义、别绑定,原样扔进 SQL”,它本身不参与参数绑定流程。
- 危险用法:
Db::select("SELECT * FROM user WHERE name = " . Db::raw("'{$name}'"))——Db::raw()仅包裹了带引号的字符串,外面仍是拼接,毫无防护力 - 安全用法:
Db::select("SELECT * FROM user WHERE created_at > " . Db::raw("DATE_SUB(NOW(), INTERVAL 7 DAY)")),里面是硬编码的 SQL 函数,无用户输入 - 动态字段(如排序)必须白名单校验:
$sort = in_array($request->input('sort'), ['created_at', 'status'], true) ? $request->input('sort') : 'id'; Db::orderBy($sort, 'DESC');
事务内原始 SQL 同样要绑定,不能因“已加事务”放松警惕
事务只保证原子性,不提供任何注入防护。很多人在 Db::transaction() 闭包里继续字符串拼接,以为“已经很安全了”,结果反而更危险——因为事务常用于资金、权限等高敏操作。
- 错误示范:
Db::transaction(function () use ($uid, $amount) { Db::update("UPDATE account SET balance = balance + {$amount} WHERE uid = {$uid}"); }); - 正确做法:
Db::transaction(function () use ($uid, $amount) { Db::update("UPDATE account SET balance = balance + ? WHERE uid = ?", [$amount, $uid]); }); - 注意:即使
$amount是整数,也必须走绑定;协程环境下类型可能被污染,(int)$amount不足以替代绑定
最易被忽略的一点:Hyperf 默认没关 PDO 模拟预处理。如果你用的是自建 PDO 连接或低版本配置,PDO::ATTR_EMULATE_PREPARES 可能为 true,此时 ? 占位实际仍是字符串拼接。上线前务必确认:$pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES) === false。


















