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'

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

相关推荐
西京刀客6 分钟前
密码学之柯克霍夫原则(Kerckhoff原则)
安全·密码学
Gworg14 分钟前
创建HTTPS网站
安全·https·ssl
永乐春秋1 小时前
WEB-通用漏洞&SQL注入&CTF&二次&堆叠&DNS带外
数据库·sql
Smartdaili China1 小时前
如何在 Microsoft Edge 中设置代理: 快速而简单的方法
前端·爬虫·安全·microsoft·edge·社交·动态住宅代理
儒道易行2 小时前
【DVWA】RCE远程命令执行实战
网络·安全·网络安全
Hacker_LaoYi3 小时前
网络安全与加密
安全·web安全
Koi慢热3 小时前
路由基础(全)
linux·网络·网络协议·安全
武子康5 小时前
大数据-230 离线数仓 - ODS层的构建 Hive处理 UDF 与 SerDe 处理 与 当前总结
java·大数据·数据仓库·hive·hadoop·sql·hdfs
hzyyyyyyyu5 小时前
内网安全隧道搭建-ngrok-frp-nps-sapp
服务器·网络·安全
网络研究院5 小时前
国土安全部发布关键基础设施安全人工智能框架
人工智能·安全·框架·关键基础设施