直接拼接用户输入到sql字符串中是高危操作,无论thinkphp版本新旧,都会绕过参数绑定防护导致sql注入;必须改用占位符或数组条件,并对动态字段、表达式严格白名单校验。

直接拼接字符串进 SQL 就是高危操作,无论 ThinkPHP 版本多新,Db::query() 或 whereRaw() 里用 "id = ".$id 这种写法,等于给攻击者开了数据库后门。
为什么字符串拼接在 ThinkPHP 里特别危险
ThinkPHP 的查询构造器(如 where(['id' => $id]))默认走参数绑定,但一旦你切到字符串上下文,框架就不再干预——whereRaw('id = '.$id)、Db::query("SELECT * FROM user WHERE name = '$name'") 这类写法完全绕过所有防护机制。
常见错误现象:
- 用户传
id=1%20OR%201=1,结果查出全表数据 - 用
input('id')拿到字符串"1' AND SLEEP(5)-- ",直接拼进去,DB 延迟响应暴露漏洞 - 以为
I('id')(TP3)或input('id/s')(TP5+)能防 SQL 注入,其实它只做字符串转义,不参与 SQL 构建阶段
哪些地方绝对不能字符串拼接
以下位置只要出现用户输入 + 字符串连接,就必须重构:
-
Db::query()的第一个参数(纯 SQL 字符串) -
whereRaw()、havingRaw()、orderRaw()的第一个参数 - 手动拼
"SELECT * FROM {$table} WHERE ..."中的$table或字段名 - 用
$_GET['order']拼ORDER BY $field,没做白名单校验
修复原则:能用数组条件就不用字符串;必须用字符串时,只允许占位符 ? 或命名参数 :name,且参数单独传入。
示例对比:
// ❌ 危险
Db::name('user')->whereRaw("username = '".$_POST['user']."'")->find();
<p>// ✅ 安全(占位符)
Db::name('user')->whereRaw('username = ?', [input('user')])->find();</p><p>// ✅ 更推荐(数组条件,自动类型推导)
Db::name('user')->where('username', input('user/s'))->find();
</p>
EXP 表达式和数组键注入怎么防
攻击者常通过 id[exp]=eq&id[0]=1 或 where[id][exp]=gt&where[id][0]=1 绕过简单过滤,触发 ThinkPHP 内部表达式解析逻辑,把用户输入当 SQL 操作符执行。
关键点:
- TP3 的
I()会默认过滤EXP|NEQ|GT等关键字,但 TP5+ 的input()不做这事——它只取值,不干预结构 - 数组型参数(如
where)若直接透传给where(),框架会尝试解析键名,exp、in、like都可能被当作指令执行 - 防御不是靠“过滤掉 exp 字符串”,而是拒绝非预期结构
实操建议:
- 禁用动态表达式:不用
where(input('where/a')),改用明确字段+值对,如where('status', input('status/d')) - 白名单控制字段:用
only(['id', 'name', 'status'])提前筛掉非法键名 - 对含
exp、in、like等键的数组参数,直接拦截并记录告警,不要试图“清洗”后再用
input() 的第三个参数不是万能解药
input('id', 0, 'intval') 看似安全,但它只在取值时起作用,后续如果又把该变量拼进字符串,前面的转换就白做了。
典型陷阱:
-
$id = input('id', 0, 'intval'); Db::query("SELECT * FROM user WHERE id = $id");——intval防不住负号溢出或科学计数法绕过(如1e9) -
$name = input('name', '', 'htmlspecialchars'); Db::name('user')->where("name LIKE '%$name%'")->find();——htmlspecialchars把单引号变成',但 SQL 里没这玩意,反而导致语法错误或逻辑失效
真正可靠的做法是:按用途选处理方式,且保持上下文一致。
比如:
- ID 查询 →
input('id/d')(强制整型) +where('id', $id) - 模糊搜索 →
input('keyword/s')(字符串) +where('name', 'like', "%{$keyword}%") - 回调地址 →
input('callback')+ 白名单校验in_array($callback, ['cb1', 'cb2']),不走任何过滤函数
最易被忽略的一点:input() 的过滤函数只运行一次,不会链式生效;input('x', '', 'trim|htmlspecialchars') 是无效写法,第二个竖线会被当成字符串字面量,不是管道符。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











