ThinkPHP 6 防止 SQL 注入的核心是启用 PDO 真实预处理并禁用模拟,同时在代码层统一使用数组条件或问号占位符传参。开发者必须显式关闭 PDO 模拟预处理配置,并严格禁止在 where() 或原生 SQL 中拼接用户输入字符串。
先说结论:ThinkPHP 6 默认开启 PDO 预处理,但需手动关闭模拟模式并规范写法才能彻底防御。
- 先判断:确认数据库配置中 PDO::ATTR_EMULATE_PREPARES 是否设为 false。
- 优先做:将所有 where() 字符串条件改为数组或闭包写法,原生 SQL 强制使用?占位符。
- 再验证:检查 runtime/log/sql.log 日志中是否出现 Binding 参数绑定记录。
命令速用版
在 config/database.php 或.env 文件中添加以下配置,强制关闭 PDO 模拟预处理:
\'params\' => [\PDO::ATTR_EMULATE_PREPARES => false]
查询代码统一改为数组传参模式,避免字符串拼接:
Db::name(\'user\')->where([\'id\' => input(\'get.id\')])->find();
为什么会这样
ThinkPHP 6 底层基于 PDO,若开启模拟预处理,占位符会被框架在 PHP 层替换为字符串,导致注入防御失效。框架的查询构造器仅在接收数组或闭包时自动触发参数绑定,字符串形式的 where 条件会被直接解析为 SQL 片段。
PDO 模拟预处理在 MySQL 5.6 及以下版本或特定 Docker 环境中默认可能为 true,此时所有?占位符都会退化为字符串拼接。where() 方法接收字符串时不做任何参数绑定,攻击者可通过构造恶意闭合引号注入 SQL 逻辑。
分步处理
第一步:修改数据库连接配置。打开 config/database.php,定位到连接配置的 params 项,显式添加 PDO::ATTR_EMULATE_PREPARES => false,不能依赖默认值。
第二步:规范 where 条件写法。禁止使用 where("name = \'" . input(\'name\') . "\'"),改为 where([\'name\' => input(\'name\')]) 或 where(\'name\', input(\'name\'))。
第三步:原生 SQL 使用占位符。Db::query() 和 Db::execute() 必须使用?占位符配合顺序参数数组,例如 Db::query("SELECT * FROM user WHERE id = ?", [$id])。
第四步:动态字段白名单校验。order()、group()、having() 不支持参数绑定,用户输入必须经过 in_array 白名单校验,例如 $sort = in_array(input(\'sort\'), [\'id\', \'create_time\']) ? input(\'sort\') : \'id\'。
怎么验证是否生效
开启应用调试模式或配置 SQL 日志,执行一次带用户输入的查询后,检查 runtime/log/sql.log 文件。安全的查询日志中应出现 Binding: [123] 形式的参数绑定记录,而非仅显示 SQL: WHERE id = 1 这样的完整拼接语句。
若日志中 SQL 语句直接包含了用户输入的具体值且无 Binding 记录,说明预处理未生效,存在注入风险。
常见坑
IN 查询不能直接绑定数组。在原生 SQL 中写 WHERE id IN (?) 并传入数组会导致生成 IN (\'1,2,3\') 错误,需动态生成对应数量的?占位符。
禁用 raw 和 exp 表达式。where([\'field\' => [\'exp\', \'...\']]) 或 Db::raw() 会完全绕过框架安全层,等同于手动拼接 SQL。
闭包内拼接仍危险。闭包查询虽默认安全,但若在闭包内使用字符串拼接 where(\'name = "\' . $name . \'"\') 或 order(input(\'sort\')) 依旧会导致注入。
命名占位符兼容性问题。ThinkPHP 6 默认配置下命名占位符如:name 可能不生效,除非手动开启 params_bind => true,建议坚持使用?顺序占位符。
常见问题
ThinkPHP 6 默认是否防 SQL 注入?
默认启用 PDO 预处理,但若配置不当或写法错误仍会失效。必须显式关闭 PDO 模拟预处理并使用数组或占位符传参。
原生 SQL 查询如何安全传参?
必须使用 Db::query("SQL ?", [$param]) 形式,禁止在 SQL 字符串中拼接变量,且不支持直接绑定表名或字段名。
order _by 字段如何防止注入?
order 方法不支持参数绑定,必须使用白名单校验用户输入的字段名,禁止直接拼接 input() 结果。
参考来源
ThinkPHP 6 中如何通过底层驱动配置防御 SQL 注入
ThinkPHP6.x 安全防御:防 SQL 注入、XSS 攻击与 CSRF 伪造的实战技巧
ThinkPHP6.0 如何防止 SQL 注入_ThinkPHP6.0 安全防护指南【安全】
ThinkPHP 怎么防止 SQL 注入攻击_ThinkPHP 数据库安全查询最佳实践方案【指南】
怎样在 ThinkPHP 6 中彻底杜绝 SQL 注入_开启强制路由与严格过滤
ThinkPHP 6 框架如何预防 SQL 注入_使用 Query 类的闭包查询
ThinkPHP 如何防止 SQL 注入_SQL 注入防御配置【指南】
ThinkPHP 报 Database query error 的 SQL 注入防御与参数绑定技巧