一聚教程网:一个值得你收藏的教程网站

热门教程

TP5.1 如何防止 SQL 注入?预处理语句与特殊字符转义详解【安全】

时间:2026-07-27 08:05:53 编辑:袖梨 来源:一聚教程网

TP5.1默认对where()等链式方法启用PDO预处理防注入,但raw()、动态字段未校验、全局过滤失效等仍会导致漏洞;Db::query()需手动绑定参数;动态元数据必须白名单校验,预处理无法防御二次注入。

TP5.1 默认对 where()find()select() 等链式查询方法启用 PDO 预处理 + 参数绑定,只要不手动拼接 SQL 字符串,就天然防基础注入。但真实项目里,90% 的注入漏洞都出在“看起来像安全写法”的地方——比如误用 raw()、动态字段未校验、或全局过滤配置失效。

where() 为什么有时还是被注入?

因为框架只对「参数化结构」做绑定,不对字符串拼接做拦截。下面这些写法看似用了 where(),实则完全绕过预处理:

  • where('id = ' . input('id')) —— 直接字符串拼接,input('id') 是什么,SQL 就执行什么
  • where('title like "%' . input('q') . '%"') —— 双引号内拼接 + 无占位符,% 和引号都由用户控制
  • where($field . ' = ?', [$value]) —— $field 是动态字段名,PDO 不绑定字段名,只绑定值

正确写法是让框架识别操作意图:where('title', 'like', '%' . input('q') . '%')where(['title' => ['like', '%' . input('q') . '%']]),这类结构会触发自动参数绑定。

Db::query() 必须手动绑定参数

一旦你写了原生 SQL(比如带子查询、JSON_EXTRACT、窗口函数),Db::query() 就不会自动帮你绑定——它只负责执行,不负责安全。这时候必须显式用问号或命名占位符:

  • 安全:Db::query('SELECT * FROM user WHERE status = ? AND level > ?', [input('status'), input('level')])
  • 安全:Db::query('SELECT * FROM user WHERE name = :name', ['name' => input('name')])
  • 危险:Db::query("SELECT * FROM user WHERE name = '" . input('name') . "'") —— 单引号也救不了你

注意:Db::bind() 是可选语法糖,本质和数组绑定等效;但如果你参数多、类型杂(比如含 NULL 或布尔值),用 bind() 显式声明更可控。

全局过滤配置(default_filter)到底靠不靠谱?

TP5.1 的 'default_filter'=>'htmlspecialchars,addslashes,strip_tags' 看似一劳永逸,但它只作用于 input()param() 等入口函数获取的变量,且顺序和语义容易翻车:

  • addslashes() 不是防 SQL 注入的正解——它不能处理 Unicode 多字节编码绕过,也不兼容所有数据库连接字符集
  • htmlspecialchars() 是防 XSS 的,对 SQL 完全无效;混在里面反而让人误以为“已防护”
  • 如果请求里带 JSON 或 Base64 编码数据,过滤链可能提前破坏原始格式,导致业务逻辑出错

真正该做的,是在数据进入 SQL 前一刻才决定怎么处理:参数化优先,实在要拼接就用 mysqli_real_escape_string($connection, $str)(且必须传有效连接资源),而不是依赖入口层“一刀切”过滤。

raw() 和 exp() 是明确放弃防御的开关

raw()(TP6)和已废弃的 exp()(TP5)本质是告诉框架:“这段我来保证安全,你别插手”。一旦用了,框架跳过所有转义、绑定、校验,原样插入 SQL:

  • where('id', 'in', raw('(' . input('ids') . ')')) —— 如果 input('ids')1,2,3); DROP TABLE user; --,直接执行
  • order(raw(input('sort'))) // 输入 "id DESC; SELECT * FROM config" → 语法错误或更糟

除非你严格白名单校验了输入(比如 in_array(input('sort'), ['id', 'name', 'created_time'])),否则永远不要把用户输入喂给 raw()。动态表名、字段名、排序方向、分组条件——这些都属于“元数据”,必须走白名单,不能靠过滤或转义。

最常被忽略的一点:预处理防不住“二次注入”。比如你把用户输入存进数据库时没过滤,之后又从库中读出来拼进新 SQL,那第一次入库时的“安全”就毫无意义。防注入不是某个函数的事,而是从输入、存储、再到输出的全链路约束。

热门栏目