Hyperf原始SQL查询必须使用Db::select()或Db::statement()配合参数绑定,严禁字符串拼接;占位符需严格对应顺序或命名,用户输入须经白名单校验与业务层权限控制。

Hyperf原始SQL查询必须用Db::select()或Db::statement()配合参数绑定
直接拼接字符串进SQL是高危操作,哪怕只是拼接一个ID或用户名。Hyperf的Db::select()和Db::statement()底层强制走PDO预处理,但前提是——你得把变量作为第二个参数传进去,而不是用sprintf或str_replace塞进SQL字符串里。
错误示范:Db::select("SELECT * FROM user WHERE id = {$id}"); —— 这完全绕过了预处理,$id是啥,SQL就执行啥,SQL注入大门敞开。
正确写法只有一种模式:Db::select("SELECT * FROM user WHERE id = ? AND status = ?", [$id, $status]); 或带命名占位符:Db::select("SELECT * FROM user WHERE id = :id AND status = :status", ['id' => $id, 'status' => $status]);
- 问号占位符(?)按顺序绑定,数组元素顺序必须严格对应
- 命名占位符(:xxx)更安全,尤其在重复使用同一变量时,比如
WHERE created_at BETWEEN :start AND :end - 即使参数是整数,也必须走绑定;不要试图“类型判断后直插”,协程环境里类型可能被污染
别碰Db::raw()当参数用,它不参与绑定
Db::raw()是给字段名、函数、表达式用的,比如SELECT Db::raw('COUNT(*) as total')或WHERE Db::raw('DATE(created_at) = CURDATE()')。但它不是变量容器,也不能用来“包裹用户输入”。
常见踩坑:Db::select("SELECT * FROM user WHERE name = " . Db::raw("'{$name}'")) —— 这等于没绑,Db::raw()只是告诉查询构造器“这段别转义”,但外面还是字符串拼接。
真正需要动态字段或函数时,应确保Db::raw()内部是白名单控制的固定值,例如:Db::raw("CONCAT(first_name, ' ', last_name)"),而不是Db::raw($userControlledField)。
- 所有用户可控内容,包括排序字段(
ORDER BY $field)、分组字段、LIMIT偏移量,都必须走白名单校验+绑定,不能靠Db::raw()伪装安全 - 如果真要动态列名,先查配置表或硬编码白名单:
in_array($sortField, ['created_at', 'updated_at', 'status'], true) ? $sortField : 'id'
事务内执行原始SQL更要严守绑定规则
事务本身不提供SQL注入防护。很多人以为“用了Db::transaction()就安全了”,结果在事务闭包里照样拼SQL:Db::transaction(function () use ($uid) { Db::update("UPDATE balance SET amount = amount + 100 WHERE uid = {$uid}"); }); —— 一样被注入,而且更危险:攻击者可能借机扣款、清零、越权修改。
事务中所有原始SQL调用,必须和顶层查询一样遵守绑定规范:
- 用
Db::update("SET amount = amount + ? WHERE uid = ?", [$delta, $uid]) - 避免在事务中调用
exec()、query()等PDO原生方法,它们脱离Hyperf连接池和绑定机制 - 如果逻辑复杂到必须用存储过程,确保过程体内部也做输入校验,不能信任传入的
IN参数
绑定不能替代业务层输入过滤
预处理防止的是SQL语法层面的破坏,但拦不住语义层面的越权或误操作。比如Db::select("SELECT * FROM orders WHERE user_id = ?", [$userId])能防注入,但如果$userId来自前端URL参数且未校验归属关系,攻击者换一个ID就能看别人订单。
所以绑定只是基础防线,后面还得跟:
- 用户ID类关键参数,必须在校验中间件里确认是否属于当前登录人(
Auth::id() === (int)$userId) - 敏感操作(如删除、导出)需二次确认或风控拦截,不能仅靠SQL安全
- 日志记录原始SQL时,绑定参数必须脱敏,例如把
['password' => 'xxx']记为['password' => '[REDACTED]'],否则审计日志反成泄露源
最易被忽略的一点:很多团队开了DB_DEBUG=true查问题,但忘了生产环境关掉——一旦SQL报错,PDO会把完整SQL连同绑定参数一起打到错误页,等于把脱敏前的数据直接喂给攻击者。


















