sqli-labs

这是一个印度小哥写的SQL注入漏洞复现的实验平台,用来练习一些基本的SQL注入技巧。在Github上面可以自由下载,搭建过相关的环境之后就可以使用了。

Less-1

进来之后的页面显示让你输入一个值。

image-20201003204150484

url中get让id=1之后返回了一个账号的名字和密码。猜测这里直接用了ID加入了SQL查询的语言,然后返回SQL查询之后的结果显示。

image-20201003204224823

这个负责构造时传入ID值的变量可能是需要闭合的,比如这个变量可能会用 '' "" ("") ('') 这一类的符号把这个变量给闭合,当然也有可能什么都不加入,也就是不构造闭合。

那么我们需要确定这是哪一种情况方法很简单,就是在Get传入参数的时候,后面加入对应的符号,如果使用 "" 来闭合,那么我们后面加上 ' 符号就不会报错,如果加入 " 符号就会报错, 也就是 'id=$id"' 没问题,但是 "id=$id"" 就会有问题,因为这里多出了一个 " 这个符号使得双引号无法闭合,所以就会报错。

image-20201003204309534

通过尝试,我们发现是通过单引号闭合的,因为加入 " 和其他符号是没有问题,但是加入单引号 ' 就会报错,说明这个负责存储ID的值的变量是通过单引号闭合。

好了,那么现在我们就可以往里面注入一些其他的命令了。我们的目标是一次性拿到所有的账号名和密码!

上面输出的账号密码肯定是那个SQL表单里面的某两列的数据,我们现在需要确定到底是哪两列,使用的方法就是联合查询,也就是 union 语句,这个是用来合并两个查询语句的数据然后再输出的,但是我们可以利用它将题目代码不输出(让ID=-1,因为没有ID=-1的数据,那么它就不会输出),而后面我们联合查询的数据却可以输出,这样我们就可以看到我们想要的数据了。我们最后需要加入注释符 -- 这样可以注释掉后面它本身的SQL语句,不干扰我们注入的语句的执行。

1
id=-1' union select 1,2 --

根据联合查询 union 的特性,需要查询的表单的列数量是一样多的才行,那么我们就可以一个一个尝试,上面的select 1,2 就是测试保存账号密码的表单是否只有两列,发现报错,说明不是两列。

然后再加数字:

1
id=-1' union select 1,2,3 --

这次成功了:

image-20201003205626333

在账号名这里输出了2,在密码这里输出了3,说明用户名保存在表单的第二列,而密码保存在表单的第三列。通过上面的联合查询,我们就可以在第二列通道和第三列通道里面通过我们精心构造的SQL语句输出我们想要的数据了。

我们想要在一行输出所有的账号或者密码,就需要使用 group_concat()函数,作用是将分到一组的数据变成一行字符串,所以我们就可以把所有的用户名变成一行字符串,所有的密码变成一行字符串。

我们首先需要用database()来知道当前这个表单所在的数据库的名字,放到第二通道或者第三通道任意一个输出即可。

image-20201003210340389

发现现在所在的数据库叫作 security ,然后我们就可以通过information_schema 这个库里面存储着整个数据库所有的信息,来定位存储着账号密码的表单的名称以及账号列和密码列的列名了。

首先这个库里面保存数据库的表单的信息是 tables 这个库,里面 table_schema 这一列里面存储着某一个表单所属的数据库的名称,然后我们就可以通过查询这个库并且筛选所属数据库名称来确定 security 这个库里面的所有表单的名字了。

1
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' --

image-20201003211807055

这想想就知道肯定在 users 这个表单里面了啊,剩下的就好办了,下一步就是获取users表单里面的列名。

1
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users' --

image-20201003212501659

好的,现在我们已经成功的拿到了列名,显然我们比较关注的是 usernamepassword 两个列,那么我们就让他们分别在2、3通道里面显示吧。

1
?id=-1' union select 1,group_concat(username),group_concat(password) from users --

然后就成功拿到了所有的数据:

image-20201003213006957

大概就写一下第一关的思路,用来快速上手以应对遗忘。

报错注入

emmmm,对了,还有几种报错注入的方式也记录下来。

group by 语句报错

这里面最复杂的就是利用group by语句执行的时候的一些特性,来构造一个错误语句,从而在返回错误信息的同时,返回我们想要的数据。

这样的结构是:

1
2
select concat((select database()),floor(rand(0)*2))
as A from information_schema.table group by A;

这里利用的是在执行group by 语句的时候,会生成一个动态的表,这个动态的表就是合并数据的过程,首先会先读入一行数据,然后查看动态表中是否用这个数据,如果有了,那么久合并,如果没有,那么就插入。

因为floor(rand(0)*2)这个东西用了随机函数,会随机产生0和1,所以每次执行结果是不确定的,那么在第一次查询动态表是否有这个数据的时候,得到的随机数0的确是可能是没有的,但是在下一步进行插入数据的时候,它又随机出来1,如果这个时候表里面是有随机数1的话,就会报错:’ Duplicate Entry’ 我们就可以在里面私藏返回我们想要的数据。比如上面的 select database() 。

但是注意的是,group_concat()会影响 group by 语句的执行

updatexml()函数报错

updatexml()函数就更好用了,他就是利用格式错误,来返回我们想要的数据。其中,这个函数的参数传递为:updatexml(string,xml,string),这里面的string可以瞎填,表示需要更新的内容,然后xml填写的是我们想要获得的信息,但是前面需要使用concat()函数加上字符 ‘‘ ,在xml格式里面,是没有以 ‘‘ 为开头的语句的。所以我们就构造了一个错误,并且这个’~’后面会报错返回我们想要的数据:

1
select updatexml("string",concat('~',(select database())),"string");

这样就返回了当前使用的数据库信息啦!

extractvalue()函数报错

这个函数使用方法和updatexml()是一样的,唯一不同的就是里面的参数就是extractvalue(“string”,xml),所以在构造的时候,只要这样写就可以了:

1
select extractvalue("string",concat('~',(select database())));