直接拼接字符串查数据库会中招,因为用户输入内容进入Db::query()等SQL字符串即交出执行权,如id=1'; DROP TABLE user; --可真删表;ORM模型默认预处理绑定参数,天然隔离语义与数据。

为什么直接拼接字符串查数据库会中招
因为用户输入的任何内容,只要进了 Db::query() 或 Db::execute() 的 SQL 字符串里,就等于把执行权交给了对方。比如传入 id=1'; DROP TABLE user; --,不加处理就会真删表。
ORM 模型(如 UserModel)默认走预处理机制,所有变量都作为参数绑定,数据库引擎天然隔离语义与数据。
- 用
where('name', 'like', '%'.$keyword.'%')是错的——字符串拼接已发生,$keyword里的单引号或注释符照样生效 - 正确写法是
where('name', 'like', "%{$keyword}%"),ThinkPHP 会自动转成参数绑定,不是字符串拼接 - 连
whereRaw()都危险:它绕过参数绑定,除非你明确用whereRaw('age > ? AND status = ?', [$age, $status])
哪些查询方式默认安全,哪些必须手动防护
ThinkPHP 的模型方法基本都内置了参数绑定,但边界很清晰:
- 安全(默认绑定):
where()、whereIn()、order()(仅字段名,不接受表达式)、limit()、find()、select() - 高危(需人工校验或改写):
whereRaw()、buildSql()、Db::query()、Db::table()->insert()中手动拼的 SQL 字符串 - 特别注意
field():传入用户控制的字段名(如$_GET['sort_field'])会直接进 SQL,必须白名单校验,不能只过滤关键词
预处理失效的典型场景和补救办法
即使用了 ORM,以下情况仍可能绕过绑定机制:
立即学习“PHP免费学习笔记(深入)”;
- 动态表名:如
Db::name($user_table)->select()——$user_table必须从固定列表取,不能来自请求参数 - 动态字段排序:
order($field.' '.$sort)——$field和$sort都要限制为['id','name','create_time']和['asc','desc'] - LIKE 模糊搜索的通配符位置:用
where('name', 'like', "%{$keyword}%")安全;但若写成where("name LIKE '%{$keyword}%'")就退化为原始 SQL,绑定失效 - 批量更新时用
update(['score' => $score + 1])是安全的;但写成update(['score' => 'score + 1'])就变成 SQL 表达式,无法绑定
验证是否真防住了,一个快速自查点
在测试环境故意传入 admin' OR '1'='1 这类 payload,观察日志里最终执行的 SQL:
- 如果看到
WHERE username = ?后面跟着Params: ["admin' OR '1'='1"],说明绑定生效 - 如果看到
WHERE username = 'admin\' OR \'1\'=\'1'(即做了 addslashes),那是错误的防御——逃逸依然可能,且破坏业务逻辑 - 开启
app_debug = true时,SQL 日志在runtime/log/下,搜Query:行最直观
真正麻烦的从来不是“会不会用 ORM”,而是那些看似 ORM 实际已滑出绑定边界的边缘写法——它们安静地躺在代码里,直到某次上线新接口才暴露。



















