ThinkPHP防SQL注入核心是强制参数绑定:一、where条件须用数组或闭包,禁字符串拼接;二、原生SQL必须用Db::query($sql, $params)显式绑定;三、关闭PDO模拟预处理与raw/exp危险接口;四、输入需强转类型并白名单校验;五、通过SQL日志和Xdebug验证绑定真实生效。

如果您在使用ThinkPHP开发应用时,发现数据库查询行为异常或日志中出现可疑SQL片段,则可能是由于用户输入未受控地进入查询语句。以下是防止SQL注入的多种防御配置与实践方法:
一、强制使用数组或闭包形式的where条件
ThinkPHP仅对数组格式(如where(['id' => $id]))和闭包格式(如where(function($q) use($name){$q->where('name', $name);}))自动启用PDO参数绑定;字符串拼接形式(如where("id = " . input('id')))会跳过绑定机制,直接拼接执行。
1、将所有动态查询条件改写为键值数组形式,例如:where(['username' => input('username'), 'status' => ['in', [0, 1]])
2、对含逻辑分支的复杂条件,封装为闭包,确保内部调用仍走绑定路径
立即学习“PHP免费学习笔记(深入)”;
3、禁用任何含变量插值的双引号字符串写法,如"name LIKE '%{input('kw')}%'"必须替换为['like', '%' . input('kw') . '%']
二、严格限制原生SQL的使用并启用显式绑定
Db::query()与Db::execute()默认不进行任何转义或绑定,必须人工介入参数隔离。框架仅在明确传入参数数组且SQL含占位符时才触发PDO绑定流程。
1、使用位置占位符时,调用Db::query("SELECT * FROM user WHERE id = ? AND status = ?", [$id, $status])
2、使用命名占位符时,确保bind参数为关联数组:Db::query("SELECT * FROM user WHERE name = :name", [':name' => input('name')])
3、对IN语句,禁止直接传入数组变量,应生成对应数量的?占位符:$placeholders = implode(',', array_fill(0, count($ids), '?'));再执行Db::query("WHERE id IN ($placeholders)", $ids)
三、关闭危险接口与模拟预处理模式
PDO的PDO::ATTR_EMULATE_PREPARES设为true时,预处理会被退化为字符串拼接,尤其在MySQL低版本或Docker环境中极易触发;raw()与exp()接口则完全绕过框架安全层,等同于手动拼接。
1、在数据库连接配置中显式关闭模拟预处理:'PDO::ATTR_EMULATE_PREPARES' => false
2、全局禁用raw()调用,如需动态字段排序,改用白名单校验:in_array(input('sort'), ['id', 'create_time']) ?: 'id'
3、删除所有where(['field' => ['exp', "..."]])用法,该结构允许任意SQL片段注入
四、强化输入过滤与类型强制转换
I()方法虽默认执行htmlspecialchars,但该操作仅防XSS,对SQL无防护作用;真正有效的输入控制需在进入查询前完成类型确认与范围约束。
1、对ID类参数立即执行整型转换:(int)input('id')或filter_var(input('id'), FILTER_VALIDATE_INT)
2、对字符串类参数启用长度与字符集白名单:preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]{2,20}$/u', input('name'))
3、在配置文件中启用严格参数模式:'DB_PARAMS' => ['strict' => true],阻止非法字段名解析
五、启用SQL执行日志与绑定行为验证
仅凭代码写法无法确认是否真实触发PDO绑定,必须通过运行时行为验证。日志中显示WHERE id = ?不代表已绑定,需确认底层是否调用bindValue()或execute($params)。
1、开启调试与SQL解释:'debug' => true, 'sql_explain' => true,检查日志中是否出现Binding: [123]记录
2、在MySQL服务端启用general_log,比对实际执行语句是WHERE id = 123还是WHERE id = ?
3、使用Xdebug断点跟踪think\db\Connection::execute(),确认$pdoStatement->bindValue()被调用而非execute($params)直传



















