摘要本文以 SQL 注入为主题先回顾数字型注入与字符型注入的区别再重点讲解两个关键探测手段order by用于判断查询结果的列数union用于把自定义查询结果拼接到原查询后面。文章结合靶场实例详细演示了先用 order by 逐次递增探测列数、再用 union select 对齐列数找出回显点、最后把回显点替换成目标数据的完整配合流程并强调所有操作都应在合规靶场中进行。一上一篇文章我们了解了关于SQL注入的基本流程这次我们来深度剖析一下数字型注入和字符型注入1注下面操作均在合规靶场进行注下面操作均在合规靶场进行注下面操作均在合规靶场进行2我们知道数字型注入和字符型注入根本的区别就是后端执行SQL代码的时候有没有对用户输入这块进行校验例如下面这段SQL代码$sqlSELECT * FROM users WHERE id$id LIMIT 0,1; $resultmysql_query($sql); $row mysql_fetch_array($result);因为在id$id LIMIT 0,1;这里后端没有对$id进行字符处理所以导致攻击者输入{1}后后端的SQL代码就为这样$sqlSELECT * FROM users WHERE id1 LIMIT 0,1;因为原始SQL已经用单引号包裹了 $id攻击者输入的 1 带来了一个闭合单引号导致剩下的 变成了多余的语法错误。这就叫“打破原有的闭合规则这样SQL代码不会被执行而是抛出一个异常{You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 1 LIMIT 0,1 at line 1}意思就是在{1 LIMIT 0,1 at line 1}这里有一个SQL语句错误这就导致攻击者可以通过这个报错信息来反推这段SQL代码的闭合类型这也就是攻击者最喜欢看到的信息比起盲注这个反而简单了因为就等于告诉攻击者注入的结果是直接回显在前端HTML页面里的。二在SQL注入中order by 和 union 是两个非常关键的探测手段它们能帮助攻击者快速判断SQL语句的结构为后续的注入利用打下基础。1order by 的用法order by 在SQL中原本用于对查询结果进行排序但在注入探测中它有一个更重要的用途判断查询结果的列数。例如对于前面的SQL代码$sqlSELECT * FROM users WHERE id$id LIMIT 0,1;我们打开靶场SQLI-LABS/Less-1,过一遍感受一下。当在URL后面加上?id1参数后页面就把SQL代码的异常回显在页面上因为我是直接在原生的HTTP请求里加的参数浏览器自动进行了URL编码在URL编码里{ %20 } 代表空格%26代表{#号}%27则代表单引号{ },你用的如果是BPBurp Suite,则空格用{ }注释掉后面的SQL代码可以用{ -- 和 %26 }回到正题order by可以帮助我们更快的知道页面最多可以回显多少的字段同样当输入{ 1 order by 1 -- } 时后端的SQL代码就为$sqlSELECT * FROM users WHERE id1 order by 1 -- LIMIT 0,1;{ order by 这里的数字可以任意输入 -- }输入{ 3 }时页面没有报错但输入{ 4 }时页面如果爆破了那么前端回显的最大列数为{ 3 }列以此类推,例如这就可以确定页面回显的最大字段为{ 3 }列。为什么先探测列数因为后面的 union 注入要求前后两个查询的列数必须一致否则会报错。所以 order by 是 union 注入的前置步骤。二、union 的用法union 的作用是把两个查询的结果合并在一起。在注入中我们利用它来把自定义的查询结果拼接到原查询后面从而获取我们想要的数据。union 的使用前提有两个前后两个查询的列数必须相同这就是为什么要先用 order by 探测列数。前后两个查询的列类型要兼容。例如假设我们探测出原查询有 3 列那么可以这样构造$sqlSELECT * FROM users WHERE id-1 union select 1,2,3;这里的{ -1} 不是 SQL 的特殊关键字它只是传给原查询参数的一个值作用是让 union前面那一段查询查不到任何数据从而让页面显示 union 后面的 1,2,3 结果,如果页面回显了2和3那就说明第 2 列和第 3 列是回显点例如我们可以把占位符替换成想要查询的内容比如$sqlSELECT * FROM users WHERE id1 union select 1,2,username from users LIMIT 0,1;页面是这样回显的这里有一个要说的这段SQL代码后面的{ LIMIT 0,1 }是这样的{ LIMIT 0,1 }LIMIT后面的01是LIMIT的子参数意思是{ 0 }从一整个结果集里面从第0条记录也就是第一条开始{ 1 }表示是只显示第一条数据这也是为什么我们使用{ -1 } 这个非法ID因为原查询查不到数据页面留出空位正好让我们的 union select 数据补位回显。像pikachu靶场就没有对数据限制这里做一个对比方便好理解三、order by 和 union 的配合流程在实际注入中两者的配合流程通常是这样的先用order by N逐次递增探测出查询的列数。再用union select 1,2,3...对齐列数找出回显点。最后把回显点替换成目标数据如库名、表名、字段名、数据。下面我们把这个流程拆开结合前面的例子一步步走一遍看看每一步到底在做什么、为什么要这么做。第一步用 order by 探测列数。这一步是整个注入的“地基”。我们并不知道后端查询到底返回几列所以从order by 1开始逐次把数字加 1直到页面报错为止。比如在 Less-1 中order by 3页面正常order by 4页面报错就说明原查询只有 3 列。为什么必须知道列数因为 union 合并结果时前后两个查询的列数必须完全一致多一列或少一列都会直接报错导致注入失败。第二步用 union select 对齐列数找出回显点。知道了列数是 3 之后我们把查询参数改成一个查不到数据的值比如-1再拼接union select 1,2,3。这样前面那段查询返回空结果页面就会显示 union 后面的内容。如果页面上出现了数字 2 和 3就说明第 2 列和第 3 列是回显点——也就是我们可以把想要的数据“塞”进去的位置。这一步相当于在目标系统里找到了两个可以输出信息的“窗口”。第三步把回显点替换成目标数据。回显点确定之后剩下的就是“换内容”了。比如把2换成database()就能看到当前数据库名把3换成username就能看到用户名字段。以此类推我们可以一步步把库名、表名、字段名、具体数据都查出来。整个过程就像在填一张表格先确定表格有几列再找到能显示内容的格子最后往格子里填我们想要的信息。这个流程是手工注入的核心思路理解之后后续的报错注入、盲注等技巧都是在这个基础上的延伸。比如报错注入是利用数据库报错信息来带出数据盲注则是通过页面是否正常来逐字符猜测数据它们的本质都是在“探测结构”和“获取数据”这两个环节上做文章。把 order by 和 union 的配合流程吃透后面再学其他注入方式就会轻松很多。四、总结本文围绕 SQL 注入中的两个核心探测手段展开order by用于判断查询结果的列数union用于把自定义查询结果拼接到原查询后面。两者的配合流程可以概括为三步先用 order by 逐次递增探测列数再用 union select 对齐列数找出回显点最后把回显点替换成目标数据如库名、表名、字段名、具体数据。理解这个流程的关键在于把握两个前提前后两个查询的列数必须相同、列类型要兼容。这也是为什么 order by 是 union 注入的前置步骤。掌握了这套手工注入的核心思路后续的报错注入、盲注等技巧都是在这个基础上的延伸——它们的本质都是在“探测结构”和“获取数据”这两个环节上做文章。最后提醒一句以上所有操作都应在合规靶场中进行切勿用于未经授权的真实系统。