SQL 注入与 ThinkPHP 漏洞技术讲义

SQL 注入与 ThinkPHP 漏洞技术讲义

文档定位:安全培训讲义 / 技术分享稿 / 内部整改参考文档

适用对象:PHP 开发人员、安全测试人员、代码审计人员、运维与项目负责人

主题聚焦:SQL 注入原理、ThinkPHP 场景下的触发机制、常见脆弱点、审计方法、修复规范与工程化治理


目录

    1. 文档导读
    1. SQL 注入的本质与形成条件
    1. SQL 注入的常见类型
    1. SQL 注入的危害分层
    1. 为什么 ThinkPHP 项目经常出现相关问题
    1. ThinkPHP 中 SQL 注入的高发位置
    1. 典型脆弱案例拆解
    1. 攻击者通常如何验证 ThinkPHP 注入点
    1. 代码审计时的检查路径
    1. 为什么简单过滤关键字通常无效
    1. ThinkPHP 的防御与修复原则
    1. 从单点漏洞到完整攻击链
    1. 验证、复现与修复回归方法
    1. 开发中的典型误区
    1. 审计人员的实战建议
    1. 老项目整改方案
    1. 可直接复用的安全编码模板
    1. 总结与检查清单

1. 文档导读

很多文章讲 SQL 注入,只停留在"给几个 payload,看几个回显页面"的层面。这种写法适合初学者快速建立印象,但不适合做真正的技术理解,也不适合拿去指导团队整改。

这份文档的目标不是堆概念,而是把三个问题讲透:

  1. SQL 注入为什么会出现。
  2. 在 ThinkPHP 项目中,问题通常藏在什么位置。
  3. 怎样从代码、规范和工程治理三个层面把它真正压下去。

如果你是开发人员,重点看第 5 节到第 11 节。

如果你是安全测试或代码审计人员,重点看第 6 节到第 16 节。

如果你是做内部培训或分享,这份文档也可以直接作为讲稿底稿。

图 1:SQL 注入整体攻击路径图


用户输入

URL 参数 表单 JSON 请求
应用接收参数
是否安全处理
参数化查询 / 白名单约束
数据库按数据处理输入
业务正常返回
字符串拼接 / 原生表达式 / 动态结构位
SQL 语义被攻击者改变
数据读取
认证绕过
数据篡改
进一步控制系统或后台


2. SQL 注入的本质与形成条件

2.1 SQL 注入到底是什么

SQL 注入的本质,不是"数据库被攻破"这么简单,而是:

应用程序把本应作为数据处理的外部输入,错误地带入了 SQL 语句的语法结构中。

一旦这个边界被打穿,攻击者就不再只是提交参数,而是在参与构造 SQL。

换句话说,真正的问题不是某个特殊字符串,而是"数据"和"代码"的边界失守了。

2.2 一个最简单的错误示例

php 复制代码
$id = $_GET['id'];
$sql = "SELECT * FROM user WHERE id = $id";

正常请求:

text 复制代码
id=1

SQL 为:

sql 复制代码
SELECT * FROM user WHERE id = 1

恶意请求:

text 复制代码
id=1 OR 1=1

SQL 变为:

sql 复制代码
SELECT * FROM user WHERE id = 1 OR 1=1

此时查询条件被改写,原本只该查一条记录的语句,可能变成整表返回。

2.3 SQL 注入形成的三个必要条件

SQL 注入通常同时满足以下三个条件:

  1. 应用接收了外部可控输入。
  2. 输入在进入 SQL 前没有被正确约束。
  3. 数据库把输入当成了 SQL 语法的一部分执行。

只要这三步连起来,注入就有成立空间。

2.4 一句必须记住的话

SQL 注入不是 payload 的问题,而是输入边界管理失败的问题。

这句话很关键。真正专业的防御,不是记住多少攻击语句,而是确保所有输入都无法越过边界,进入 SQL 结构位。

图 2:参数化查询与字符串拼接对比图
危险路径
用户输入: 1 OR 1=1
直接拼接进 SQL
数据库将其视为语法片段
查询结构被改写
安全路径
用户输入: 1 OR 1=1
参数绑定
数据库将其视为普通值
查询结构不变


3. SQL 注入的常见类型

理解不同类型,不是为了"打得更花",而是为了知道问题可能以什么形式暴露。

3.1 联合查询注入

通过 UNION SELECT 将攻击者构造的查询结果拼接到正常查询结果中。

典型特点:

  • 页面会直接显示查询结果。
  • 原查询列数、字段类型需要大致匹配。
  • 常见于列表页、详情页、搜索页。

3.2 报错注入

通过触发数据库错误,把目标数据夹带进错误信息中返回。

典型特点:

  • 页面或接口会返回数据库报错。
  • 开发环境、测试环境或日志泄露场景更常见。

3.3 布尔盲注

页面不直接返回数据,攻击者就利用"条件成立"和"条件不成立"时页面反馈的差异,一位一位猜解信息。

典型特点:

  • 页面有真假差异。
  • 不需要明显报错。
  • 获取数据较慢,但稳定性往往更高。

3.4 时间盲注

页面既不报错,也没有明显内容差异时,攻击者会借助数据库延时函数,通过响应时间变化判断条件是否成立。

典型特点:

  • 接口响应时间可观测。
  • 常用于深藏较深的接口或 JSON API。

3.5 堆叠注入

某些场景下,如果驱动或执行方式允许一次执行多条 SQL,攻击者可能在原语句后继续追加新语句。

这种方式能不能成立,取决于:

  • 数据库类型
  • 驱动配置
  • 框架执行方式
  • 是否允许多语句执行

因此它不是所有场景都能用,但一旦成立,破坏力通常更大。

图 3:SQL 注入类型对照图
SQL 注入类型
联合查询注入
报错注入
布尔盲注
时间盲注
堆叠注入
页面可回显查询结果
数据库错误信息可见
页面存在真假差异
可通过延迟判断条件
驱动或配置允许多语句执行

类型 页面特征 典型前提 利用成本 风险说明
联合查询注入 有回显 列数与类型可适配 读取数据效率高
报错注入 有报错 错误信息暴露 低到中 调试环境中常见
布尔盲注 有真假差异 页面逻辑可观察 稳定但较慢
时间盲注 仅时间差异 接口响应时间可测 无回显时常用
堆叠注入 取决于执行环境 支持多语句 成立后破坏力强

4. SQL 注入的危害分层

谈风险时,不能只说一句"可能泄露数据库"。这太粗糙。实际危害往往分层推进。

4.1 第一层:数据读取

最基础、最直接的后果包括:

  • 读取用户表、管理员表
  • 读取手机号、邮箱、身份证等敏感信息
  • 读取订单、支付、财务数据
  • 读取配置表、业务规则表、权限表

4.2 第二层:认证绕过

如果登录逻辑写得不严谨,攻击者可能通过构造条件绕过口令校验,直接进入后台。

4.3 第三层:数据篡改

在数据库具备写权限的情况下,攻击者可能:

  • 修改管理员密码
  • 修改账号角色
  • 删除业务数据
  • 注入后门配置

4.4 第四层:文件与系统层扩展

在高权限数据库配置下,攻击者可能进一步尝试:

  • 读取服务器敏感文件
  • 写出恶意文件
  • 获取 WebShell 落点
  • 扩展到系统控制层

4.5 第五层:横向扩展与业务接管

数据库里往往不止业务数据,还可能藏着:

  • 邮件服务凭证
  • 短信平台密钥
  • 云存储配置
  • 支付回调密钥
  • 第三方接口令牌

一旦这些信息被读到,攻击链就会迅速跨出数据库本身。

讲义提示:

做培训时,可以在这一节强调一句:

"SQL 注入常常不是终点,而是后续接管的起点。"


5. 为什么 ThinkPHP 项目经常出现相关问题

这里需要先澄清一个容易被误读的点:

ThinkPHP 不等于 SQL 注入。真正的问题往往是框架能力被误用,或者老版本项目存在历史包袱。

5.1 使用量大,老项目多

ThinkPHP 在国内项目中长期非常常见,尤其是中小型业务系统、后台管理系统、行业项目、定制项目。

这意味着:

  • 历史版本多
  • 维护水平参差不齐
  • 安全标准不统一
  • 代码风格差异大

项目越多、版本越杂、开发质量越不均衡,安全问题就越容易集中暴露。

5.2 查询构造灵活,误用空间也大

ThinkPHP 支持:

  • 链式查询
  • 数组条件
  • 动态字段
  • 表达式查询
  • 原生 SQL

这些能力本身没有问题,但只要开发者把外部输入直接带进去,风险会非常快地放大。

5.3 老项目常见"赶工式"代码

很多历史项目里最常见的不是复杂漏洞,而是这种为了快速上线的写法:

php 复制代码
$map = input('get.');
$list = Db::name('user')->where($map)->select();

或者:

php 复制代码
$order = input('get.order');
$list = Db::name('user')->order($order)->select();

又或者:

php 复制代码
$sql = "select * from user where id = " . input('get.id');
$res = Db::query($sql);

这些代码的问题不在于"不优雅",而在于直接失去了输入边界。

5.4 安全治理通常落后于业务迭代

ThinkPHP 项目很多时候不是没人知道有风险,而是:

  • 业务先跑起来
  • 安全后补
  • 老模块没人敢动
  • 接口越来越多
  • 开发规范没统一

于是本来只是一个局部问题,最后会积累成系统性风险。


6. ThinkPHP 中 SQL 注入的高发位置

这一节是整篇文档的核心。真正做审计或整改,重点就是找这些位置。

6.1 原生 SQL 查询拼接

最直接、最危险、最不该出现在核心业务里的写法:

php 复制代码
$id = input('get.id');
$res = Db::query("SELECT * FROM tp_user WHERE id = $id");

或者:

php 复制代码
$username = input('post.username');
$res = Db::query("SELECT * FROM tp_user WHERE username = '$username'");

风险原因很简单:

框架的参数绑定和查询构造器保护机制被完全绕开了。

高风险接口
  • Db::query()
  • Db::execute()
  • 模型中的自定义原生 SQL
  • 任何把用户输入直接拼进 SQL 字符串的代码

6.2 where() 被整包参数直接驱动

例如:

php 复制代码
$params = input('get.');
$list = Db::name('user')->where($params)->select();

或者:

php 复制代码
$where = input('post.where');
$list = Db::name('user')->where($where)->select();

问题在于,开发者以为自己是在"偷懒做通用查询",实际上是在把条件构造能力开放给外部输入。

6.3 排序注入:order() 是非常高频的薄弱点

后台列表页里最常见的问题往往不是 where,而是 order

php 复制代码
$order = input('get.order');
$list = Db::name('user')->order($order)->select();

这类代码出现频率非常高,因为排序功能看起来不像"危险点",开发者防范意识普遍偏弱。

6.4 字段、表名、联表等结构位可控

比如:

php 复制代码
$field = input('get.field');
$data = Db::name('user')->field($field)->select();

或者:

php 复制代码
$table = input('get.table');
$data = Db::table($table)->select();

只要让外部输入控制了 SQL 结构位,问题就已经进入高危区域。

6.5 表达式查询与原生片段

例如:

php 复制代码
Db::name('user')->where('id', 'exp', "= $id")->select();

或者:

php 复制代码
$exp = input('get.exp');
Db::name('user')->where('status', 'exp', $exp)->select();

exp 的含义本质上就是"按表达式插入,而不是按普通值处理"。

这种接口只适合固定表达式,不适合任何外部输入。

6.6 raw 类接口

例如:

  • whereRaw()
  • orderRaw()
  • fieldRaw()
  • havingRaw()

这些接口只要和外部输入发生直接拼接,就应视为高风险。

图 4:ThinkPHP 高危查询接口地图
ThinkPHP 查询相关接口
原生 SQL
结构位控制
表达式与 Raw
动态条件组装
Db::query
Db::execute
order
field
table
join
group
limit
whereRaw
orderRaw
fieldRaw
havingRaw
exp
where(input get.)
where(input post.)
动态 map 条件

风险等级 典型接口 主要问题
极高 Db::query() Db::execute() 直接手工拼接 SQL
whereRaw() orderRaw() fieldRaw() exp 原生表达式直接进入执行链
order() field() table() join() 外部输入控制 SQL 结构位
中高 where($map) where(input('get.')) 条件构造权被交给前端参数

7. 典型脆弱案例拆解

下面给一个非常接近真实业务的例子:

php 复制代码
public function index()
{
    $keyword = input('get.keyword');
    $order   = input('get.order');

    $sql = "SELECT id,username,email FROM tp_user
            WHERE username LIKE '%$keyword%'
            ORDER BY $order";

    $list = Db::query($sql);
    return json($list);
}

7.1 这段代码的问题在哪里

它实际上有两个独立的注入点:

  1. keyword 进入了 LIKE 子句。
  2. order 进入了 ORDER BY 结构位。

7.2 为什么这种代码在项目里很常见

因为它完全符合"快速做一个列表接口"的思路:

  • 支持搜索
  • 支持排序
  • 一条 SQL 搞定
  • 上线很快

但这类代码的代价,就是把输入边界交了出去。

7.3 正确重构思路

安全改法不是"多过滤几个字符",而是拆分两类输入:

  • 值参数:走参数化或查询构造器
  • 结构参数:走白名单

可改成:

php 复制代码
public function index()
{
    $keyword = input('get.keyword', '', 'trim');
    $field   = input('get.field', 'id', 'trim');
    $sort    = input('get.sort', 'desc', 'trim');

    $allowFields = ['id', 'username', 'create_time'];
    if (!in_array($field, $allowFields, true)) {
        $field = 'id';
    }

    $sort = strtolower($sort) === 'asc' ? 'asc' : 'desc';

    $list = Db::name('user')
        ->field('id,username,email')
        ->whereLike('username', "%{$keyword}%")
        ->order($field . ' ' . $sort)
        ->select();

    return json($list);
}

7.4 这个重构为什么有效

因为修复点不在于"替换了几个函数",而在于边界重新建立了:

  • keyword 只作为值使用
  • 排序字段来自白名单
  • 排序方向被限制为固定枚举
  • 整个查询不再依赖原生 SQL 拼接

图 5:脆弱代码与安全代码对照图
脆弱实现
原生 SQL 拼接
keyword 直接进入 LIKE
order 直接进入 ORDER BY
用户输入影响 SQL 结构
安全实现
查询构造器
keyword 作为普通值
field 与 sort 白名单
用户输入无法改写 SQL 结构

text 复制代码
脆弱实现:
用户输入 -> 拼接 SQL -> 数据库按语法执行 -> 可能注入

安全实现:
用户输入 -> 类型约束 / 白名单 / 参数化 -> 数据库按数据处理 -> 查询结构稳定

8. 攻击者通常如何验证 ThinkPHP 注入点

从防守视角理解攻击路径,有助于知道自己该优先检查什么。

8.1 先看参数名

以下参数名经常值得重点关注:

  • id
  • keyword
  • search
  • sort
  • order
  • field
  • filter
  • where
  • map
  • ids

它们不一定都有漏洞,但它们常常代表"开发者想做动态查询"。

8.2 再看反馈方式

攻击者通常会观察:

  • 是否出现 SQL 报错
  • 页面内容是否有真假差异
  • JSON 结构是否异常
  • 响应时间是否明显变化

8.3 识别 ThinkPHP 项目特征

一旦判断目标是 ThinkPHP 项目,攻击者往往会更有针对性地测试:

  • 动态查询条件
  • 排序字段
  • raw 接口误用
  • exp 表达式误用
  • 历史版本遗留问题

8.4 后台管理功能尤其容易出问题

风险高发区域包括:

  • 列表页
  • 搜索页
  • 导出页
  • 报表页
  • 批量操作接口

因为这些地方天然依赖筛选、排序、统计、联表,最容易出现拼接式代码。


9. 代码审计时的检查路径

如果你在审计 ThinkPHP 项目,建议先做全局检索。

9.1 建议优先搜索的关键字

text 复制代码
Db::query(
Db::execute(
whereRaw(
orderRaw(
fieldRaw(
havingRaw(
exp
order(
field(
table(
join(
input(
$_GET
$_POST
$_REQUEST

9.2 审计时不要只看"这一行"

应按数据流来判断:

  1. 参数从哪里来。
  2. 是否可被用户直接控制。
  3. 是否经过类型约束。
  4. 是否经过白名单限制。
  5. 最终进入的是值位还是结构位。
  6. 是否被作为原生 SQL 或表达式片段处理。

9.3 几类典型高风险模式

php 复制代码
Db::query("... $var ...")
Db::execute("... {$var} ...")
->order(input('get.order'))
->field(input('get.field'))
->where(input('post.'))
->where($map)
->where('id', 'exp', $exp)

只要出现"外部输入 -> SQL 结构位 / 原生表达式"的路径,就应该继续深挖。

9.4 发现一个点,不要只修一个点

很多项目的漏洞不是孤立的,而是编码习惯导致的成片问题。

如果一个控制器里已经出现动态 order(),同风格代码里通常还会有动态 field()、动态 where()、原生 SQL 拼接等问题。


10. 为什么简单过滤关键字通常无效

很多老项目喜欢这样处理:

php 复制代码
$id = str_replace(['select', 'union', "'"], '', input('get.id'));

这种方式看起来像做了防护,实际非常脆弱。

10.1 黑名单永远补不完

你今天封了 union,明天还有大小写绕过、注释绕过、编码绕过、函数替代、逻辑替代。

10.2 注入不只依赖几个关键字

很多注入方式并不需要典型关键字,也不一定依赖引号闭合。

10.3 容易误伤正常输入

粗暴删字符常常会导致业务参数本身被污染。

10.4 结构位问题根本不是过滤能解决的

例如排序字段,正确做法不是"过滤危险字符",而是:

字段只能来自预定义白名单。

结论很明确:

  • 值参数靠参数化
  • 结构参数靠白名单
  • 不要迷信黑名单过滤

11. ThinkPHP 的防御与修复原则

这一节适合直接写进团队编码规范。

11.1 能不用原生 SQL 就不要拼原生 SQL

错误写法:

php 复制代码
Db::query("SELECT * FROM user WHERE id = $id");

推荐写法:

php 复制代码
Db::name('user')->where('id', (int)$id)->find();

11.2 值参数必须被当作值处理

比如:

  • 用户名
  • 关键字
  • 手机号
  • 状态值
  • 时间值

这些都应该进入参数化查询或查询构造器,而不是拼进字符串。

11.3 结构参数必须白名单

以下都属于结构参数:

  • 排序字段
  • 排序方向
  • 字段列表
  • 分组字段
  • 表名
  • 联表片段

这些参数不能靠"过滤"处理,只能从固定枚举中选。

11.4 禁止整包请求直接进 where

错误方式:

php 复制代码
$params = input('get.');
Db::name('user')->where($params)->select();

正确方式:按字段逐个接收、逐个校验、逐个构造。

11.5 expraw、自定义 SQL 视为高敏感能力

这些接口不是绝对禁止,但必须满足:

  1. 表达式由开发者内部固定生成。
  2. 不接受任何未经白名单约束的外部输入。

11.6 最小化数据库权限

即使发生注入,也应尽量缩小损害面。

业务库账号不应具备超出实际需要的高危权限。

图 6:错误防护思路与正确防护思路对照图
正确思路
值参数参数化
结构参数白名单
禁止外部输入进入 raw / exp
数据库最小权限
修复后做安全回归
错误思路
过滤几个危险字符
黑名单拦截关键字
只盯单引号
认为后台接口没人碰


12. 从单点漏洞到完整攻击链

SQL 注入最容易被低估的地方,就是很多人以为它只是"多查了几条数据"。

现实中,一条注入链很可能这样发展:

  1. 读取管理员表。
  2. 获取口令哈希、会话标识或重置令牌。
  3. 接管后台。
  4. 借助文件上传、插件管理、模板编辑等能力落地后门。
  5. 进一步控制服务器。

另一条常见路径是:

  1. 读取配置表。
  2. 获取邮件、短信、对象存储或第三方 API 密钥。
  3. 接管外围服务。
  4. 扩大为业务级事故。

因此整改不能只盯着"这条 SQL 改没改",还要看:

  • 数据库权限是否过高
  • 后台是否存在弱口令
  • 上传链路是否安全
  • 配置文件是否已泄露
  • 日志里是否已有攻击痕迹

13. 验证、复现与修复回归方法

这里强调一点:

复现的目标是验证风险与修复效果,而不是做破坏性展示。

13.1 验证思路

建议按以下步骤进行:

  1. 确认参数进入 SQL 的位置。
  2. 判断它是值位还是结构位。
  3. 用最轻量的方式验证是否能引起语义变化。
  4. 观察报错、真假差异、时间差异或结果异常。
  5. 修复后做同路径回归。

13.2 测试中的边界要求

  • 优先在测试环境或镜像环境进行。
  • 不要直接对生产做破坏性验证。
  • 记录测试前后的响应差异。
  • 不为了"证明漏洞"而做越界行为。

13.3 修复成功的判定标准

修复不能只看"原来的 payload 不生效了",还要看:

  • 用户输入是否只能作为数据出现
  • 结构参数是否被白名单锁死
  • 原生表达式接口是否已与外部输入隔离
  • 正常业务功能是否仍然可用

14. 开发中的典型误区

14.1 误区一:用了框架就天然安全

错。框架提供的是能力,不是免责。

14.2 误区二:只要过滤单引号就行

错。SQL 注入远不止靠引号闭合。

14.3 误区三:数字参数不会注入

错。只要数字没有被强制约束,照样可能进入表达式逻辑。

14.4 误区四:后台接口没人知道,不会被打

错。后台列表、导出、筛选接口恰恰是高发区域。

14.5 误区五:不报错就说明安全

错。还有布尔盲注和时间盲注。


15. 审计人员的实战建议

15.1 审计优先级建议

第一优先级:

  • Db::query()
  • Db::execute()
  • raw 类接口
  • exp 表达式

第二优先级:

  • 后台列表页
  • 搜索接口
  • 导出接口
  • 统计报表接口

第三优先级:

  • 动态排序
  • 动态字段
  • 动态时间范围
  • 批量 ID 参数
  • 通用筛选 map

15.2 审计时关注"模式",不要只盯"单点"

如果某个模块里已经出现:

php 复制代码
->order(input('get.order'))

那就不要只修这一行,而要继续检查整个项目是否普遍存在同类写法。

真正高效的审计,抓的是编码习惯。


16. 老项目整改方案

如果你接手的是历史较久的 ThinkPHP 项目,建议按这个顺序推进。

16.1 第一步:全局盘点

先把所有涉及以下能力的代码搜出来:

  • 原生 SQL
  • 动态排序
  • 动态字段
  • 表达式查询
  • 整包参数进 where

16.2 第二步:按风险分级

高危:

  • 原生 SQL 拼接
  • exp 接外部输入
  • raw 接外部输入

中危:

  • 动态 order
  • 动态 field
  • 动态 table

一般风险:

  • 缺少类型约束
  • 缺少参数枚举

16.3 第三步:做统一封装

例如后台列表查询统一约束:

  • 允许哪些筛选字段
  • 允许哪些排序字段
  • 允许哪些排序方向
  • 搜索统一走构造器

这样可以从机制上减少"每个控制器自己拼"的情况。

16.4 第四步:把规则纳入代码评审

建议加入 CR 检查项:

  • 禁止拼接 SQL 字符串
  • 禁止动态表名
  • 禁止外部输入直接进入 raw / exp
  • 禁止整包请求参数直接进入查询条件

16.5 第五步:修复后做回归测试

尤其要验证:

  • 搜索是否正常
  • 排序是否正常
  • 分页是否正常
  • 导出是否正常

安全整改不能以牺牲业务稳定性为代价。


17. 可直接复用的安全编码模板

下面给一个更接近实际项目的示例:

php 复制代码
public function userList()
{
    $keyword = input('get.keyword', '', 'trim');
    $status  = input('get.status/d', -1);
    $field   = input('get.field', 'id', 'trim');
    $sort    = input('get.sort', 'desc', 'trim');

    $allowOrderFields = ['id', 'username', 'create_time'];
    if (!in_array($field, $allowOrderFields, true)) {
        $field = 'id';
    }

    $sort = strtolower($sort) === 'asc' ? 'asc' : 'desc';

    $query = Db::name('user')->field('id,username,status,create_time');

    if ($keyword !== '') {
        $query->whereLike('username', "%{$keyword}%");
    }

    if ($status >= 0) {
        $query->where('status', $status);
    }

    $list = $query->order($field . ' ' . $sort)->paginate(20);

    return json($list);
}

这个模板的核心原则只有三条:

  1. 输入先分类型处理。
  2. 值参数走查询构造器或参数化。
  3. 结构参数走白名单。

如果团队统一坚持这三条,ThinkPHP 项目里的 SQL 注入风险会下降得非常明显。


18. 总结与检查清单

把整篇文档收束成一句话:

ThinkPHP 中绝大多数 SQL 注入问题,并不是框架天然不安全,而是开发者把外部输入带进了 SQL 结构位或原生表达式中。

真正该记住的,不是某条 payload,而是这四条原则:

  1. 用户输入默认不可信。
  2. 值和结构必须分开处理。
  3. 原生 SQL、rawexp 等能力要高敏感使用。
  4. 白名单和参数化永远比黑名单可靠。

18.1 自查清单

text 复制代码
[ ] 是否存在 Db::query / Db::execute 手工拼接 SQL
[ ] 是否存在 whereRaw / orderRaw / fieldRaw 接收外部输入
[ ] 是否存在 exp 表达式接收外部输入
[ ] 是否存在 order() 动态接收前端字段
[ ] 是否存在 field() / table() / join() 动态可控
[ ] 是否存在 input('get.') / input('post.') 直接进入 where
[ ] 是否对排序字段、排序方向做了白名单
[ ] 是否对 ID、状态、分页等参数做了类型约束
[ ] 数据库账号是否采用最小权限
[ ] 修复后是否做了功能回归与安全回归

18.2 适合作为结尾强调的一句话

很多人学 SQL 注入,学的是"怎么打";真正成熟的团队,学的是"为什么这个缺陷会出现,以及怎样在工程上不让它再出现"。

ThinkPHP 恰好是一个很典型的教学样本,因为它既有足够广泛的真实项目基础,也有足够典型的误用场景,特别适合用来讲清安全边界、编码规范和治理思路。


相关推荐
杰克尼2 小时前
redis(day07-Redis 最佳实践)
数据库·redis·缓存
倔强的石头1062 小时前
表空间自动目录创建与存储管理实践:参数化配置与性能优化
数据库·oracle·性能优化
不剪发的Tony老师2 小时前
Goose:一款成熟灵活的数据库变更管理工具
数据库
草莓熊Lotso2 小时前
Linux 线程深度剖析:线程 ID 本质、地址空间布局与 pthread 源码全解
android·linux·运维·服务器·数据库·c++
AcrelGHP2 小时前
安科瑞AIM-T系列工业IT绝缘监测及故障定位解决方案为关键供电场所筑牢安全防线
大数据·运维·数据库
fīɡЙtīиɡ ℡2 小时前
【Mysql——MVCC】
数据库·mysql
XDHCOM2 小时前
ORA-31477: LogMiner会话清理失败,Oracle报错故障修复远程处理,快速解决,数据安全无忧
数据库·oracle
白毛大侠2 小时前
# MySQL InnoDB 隔离级别与 MVCC 完全解析
android·数据库·mysql
weisian1512 小时前
进阶篇-LangChain篇-10--向量数据库选型指南:本地FAISS, Chroma与云原生方案
数据库·langchain·faiss·向量数据库·chroma