SQL双查询注入(Double SQL Injection)原理详解(其一)

什么是SQL双注入?

SQL双注入属于SQL报错注入的一种,即通过利用数据库的bug,将我们想要的信息放入报错信息中,在前端可以回显报错信息的前提下,完成敏感信息的获取。

背景知识


什么是双查询?

双查询指的就是在一个Select语句中再次嵌入一个select语句,如:

SQL 复制代码
SELECT COUNT((select database()))


在查询的时候,会先执行select database()语句,然后再将该语句的执行结果传递给count()函数,从内到外依次执行。 对于双查询注入,大家需要先了解count()、rand()、floor()这三个函数的功能以及group by语句的用法。

要用到的SQL函数

count()


count():用来统计表中或数组中记录数目的一个函数,count(*)表示计算查询出的表中所有的行数

floor()


floor():返回小于等于该值的最大整数,采用向下取整方式

rand() --- rand(): 产生随机数,可以指定固定种子,即调用时采用rand(0)的方式,此时随机种子固定为0

关于rand函数有一点需要说明: 如果使用 rand()函数,该函数生成的随机序列随机性较高,而指定随机种子后,如下图所示:

rand(0)的查询结果几乎消除了floor(rand()*2)函数原有的随机性,或者说每次查询时其生成的随机序列相同,连续查询几次,我们会发现它的规律如下:01101

group by子句


在表中再插入两条数据,name值都为"bbb":

sql 复制代码
mysql> INSERT INTO test VALUES("3","bbb");
mysql> INSERT INTO test VALUES("4","bbb");

成功后表如下:

这时候我们使用group by 语句时,MySQL会将查询结果分类汇总,重复的内容会合并为一项:

vbnet 复制代码
mysql> SELECT name FROM test GROUP BY name;

这时候再使用count()函数就可以对不同的条目计数:

sql 复制代码
mysql> SELECT count(*),name FROM test GROUP BY name;

如图:aaa有一条,bbb有3条

group by语句的实现原理


在MYSQL中,group by语句有五种实现方式,其中四种方式都是通过临时表来实现的,以上图中的语句为例,即:

sql 复制代码
mysql> SELECT count(*),name FROM test GROUP BY name;

在执行group by name语句时,MySQL会在内部建立一个虚拟表,用来储存列的数据,而group by字句所指明的列会作为表的主键,当查询数据时,取数据库数据,然后查看虚拟表中是否存在相同主键值的数据,不存在则插入新记录。如下图所示:

  • 图1代表mysql建立了一张空白虚拟表,其中name列作为主键,count(*)记录主键为某一值的行的数目。
  • 图2为当读取到第一行数据时,aaa不存在,将aaa放入主键列中,1放在id列中
  • 图3为,当读取name=='bbb'的数据时,发现主键没有值'bbb',于是插入新行并计数
  • 往下执行,遇到多余的bbb,已经有bbb存在,则将'bbb'行的count(*)列加1。内部情况如图4

这样就能对上面的分类结果进行统计,然后将统计结果返回:

而双查询报错的关键就在这里,主要的原因在于rand()函数在group by的过程中被触发了多次,事实上这也算MYSQL数据库的一个bug。

报错原理

让我们回看一下构造的报错语句:

sql 复制代码
mysql> SELECT count(*),concat((SELECT database()),"~",floor(rand(0)*2))as a FROM test GROUP BY a;

首先,当rand(0)函数执行五次时,其产生的序列为:01101


因此,当MYSQL运行这条语句时,虚拟表中的变化情况如下图所示:

1. 执行前虚拟表为空:

2. 第一次执行

当处理第一行时,这时的concat((SELECT database()),"~",floor(rand(0)*2))生成结果为sql_test~0,group就以sql_test~0查询虚拟表,发现表中没有该值的主键,于是将这条语句的结果 插入到虚拟表中。注意!是将这条语句的结果插入到虚拟表中,而不是将 sql_test~0 插入到虚拟表中。 或者说,在查询某一列数据是否存在于虚拟表和向虚拟表插入数据时,rand()函数都会运行一次

由于虚拟表为空,所以会将其插入到虚拟表中,这里的插入过程中,concat((SELECT database()),"~",floor(rand(0)*2))语句中的rand()函数会再次执行,即插入的值为sql_test~1

而原本我们想插入的记录实际上是sql_test~0。所以上面的情况就是用sql_test~0这个结果查询虚拟表,不存在该数据,于是插入虚拟表,插入时又运算一次,然后插入的值变成了sql_test~1,所以这就是主要的冲突,当表中没有数据时,即使查询虚拟表的值和插入虚拟表的值不是同一个,但虚拟表也只生成一条记录,不会出现问题。

然而当表的数据出现两条以上的时候,即group by 在处理完第一条数据后会往下继续处理第二条,于是第二条还会按第一条的处理方式进行:

3. 第二次执行

在处理第二行数据时,此时执行rand(0)返回的结果为1(此时对应上面01101的第三次查询结果1),生成结果为sql_test~1,MYSQL查询虚拟表发现有该条记录,于是将更新计数,注意更新计数时rand函数不会再次执行。

4. 第三次执行

取第三条记录查询,此时执行rand(0)返回的结果为0(此时对应上面01101的第四次查询结果0),发现虚拟表中没有键0,所以要将其写入虚拟表。同样在写入虚拟表的时候,rand(0)又执行了一遍,此时查询结果为上面01101的第五次结果1,但是键1已经存在虚拟表中,由于键只能唯一,所以此时就会报错。所以在使用floor()、rand(0)、count()、group by时,数据表中至少要有3条记录才会报错.

报错提示如下:

rust 复制代码
ERROR 1062 (23000): Duplicate entry 'sql_test~0' for key 'group_key'

由此,就可以通过报错提示来获取敏感信息了~

相关推荐
PcVue China2 小时前
PcVue + SQL Grid : 释放数据的无限潜力
大数据·服务器·数据库·sql·科技·安全·oracle
Hacker_Nightrain7 小时前
网络安全CTF比赛规则
网络·安全·web安全
看山还是山,看水还是。7 小时前
Redis 配置
运维·数据库·redis·安全·缓存·测试覆盖率
学编程的小程8 小时前
【安全通信】告别信息泄露:搭建你的开源视频聊天系统briefing
安全·开源·音视频
网络安全指导员8 小时前
恶意PDF文档分析记录
网络·安全·web安全·pdf
渗透测试老鸟-九青8 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
vortex59 小时前
蓝队基础之网络七层杀伤链:从识别到防御的全方位策略
安全·网络安全·蓝队
白总Server9 小时前
JVM解说
网络·jvm·物联网·安全·web安全·架构·数据库架构
kali-Myon9 小时前
ctfshow-web入门-SSTI(web361-web368)上
前端·python·学习·安全·web安全·web
xxtzaaa10 小时前
抖音如何更安全的运营多个账号 打好运营基础
安全