where()传字符串等于裸奔,必须用数组或闭包触发PDO预处理;htmlspecialchars()不该全局配置default_filter,而应按输出上下文显式转义。

where() 传字符串等于裸奔,htmlspecialchars() 不该写在配置里自动套用——这两点不改,其他措施都是补漏。
where() 必须用数组或闭包,禁用任何字符串拼接
ThinkPHP5 的where() 方法只有在接收数组(如 ['id' => $id])或闭包时才触发 PDO 预处理绑定;一旦传入字符串(哪怕加了单引号),就直接拼 SQL 执行。
常见错误现象:页面没报错,但输入 ' OR '1'='1 能绕过登录、查出全部用户。
- 错误写法:where("name = '" . input('name') . "'")、where('status = ' . $status)、where("title LIKE '%" . input('q') . "%")
- 正确写法:where(['name' => input('name')])、where('status', 'eq', $status)、where(['title' => ['like', '%' . input('q') . '%']])
- 复杂条件必须封装为闭包:where(function($query) use($a, $b) { $query->where('a', $a)->whereOr('b', $b); })原生 SQL 必须显式 bind,不能依赖 query() 自动处理
Db::query() 和 Db::execute() 默认不做任何过滤或绑定,框架只在你明确提供占位符 + 参数数组时才走预处理流程。
性能与兼容性影响:若未关闭 PDO::ATTR_EMULATE_PREPARES(尤其 Docker 环境或 MySQL 5.6 以下),模拟预处理会退化为字符串拼接,bind() 形同虚设。
- 安全写法(位置占位):Db::query("SELECT * FROM user WHERE id = ? AND status = ?", [$id, $status])
- 安全写法(命名占位):Db::query("SELECT * FROM user WHERE name = :name", [':name' => input('name')])
- IN 子句要动态生成占位符:$placeholders = str_repeat('?,', count($ids) - 1) . '?'; Db::query("WHERE id IN ($placeholders)", $ids)
- 危险写法:Db::query("SELECT * FROM user WHERE name = '" . input('name') . "'")XSS 输出在哪发生,就在哪转义,别信 default_filter
ThinkPHP5 模板引擎默认不自动 HTML 转义,default_filter 配置项会全局套用函数(如 'htmlspecialchars,addslashes'),但问题在于:
- 它对所有变量无差别处理,JSON 输出、JS 内联、URL 参数等场景会被破坏;
- 一旦配置写错(比如函数名拼错),页面不报错,只是所有变量变 null;
- XSS 防御本质是上下文敏感的,HTML 属性、JS 字符串、CSS 值需不同编码方式。
- 正确做法:仅在模板中显式转义输出:{:htmlspecialchars($user_input, ENT_QUOTES | ENT_HTML5, 'UTF-8')}
- 富文本需白名单过滤(如 htmlpurifier),不能靠 htmlspecialchars 一招打天下;
- JS 内联变量必须用 json_encode($data, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG)。
关键复杂点在于:SQL 注入和 XSS 都不是“配个选项就完事”的问题。它们依赖上下文判断——字段值要绑定,字段名要白名单,输出位置决定转义方式。最容易被忽略的是二次注入(从数据库读出恶意内容再拼进新 SQL)和模拟预处理失效(PDO::ATTR_EMULATE_PREPARES = true 时)。



















