SQL注入-Duplicate entry注入报错及原理
本文作者:无名安全团队-usename
现象
如下图,通过duplicate entry报错语句结合数据库函数暴露信息。
原理
相关函数介绍:
1.concat(str1,str2,...):返回结果为连接阐述产生的字符串。如果有任一参数值为null,则返回null
2.floor(n),n为实数:对n向下取整,并返回
3.rand():产生随机数的函数,值为(0,1)之间的随机数。传入一个固定的随机数种子后,可以形成固定发伪随机序列。如下图,直接使用rand()时,每次产生的数都不同,但是当传入随机数种子后,每次产生 的值都是一样的,如rand(0)
所以当rand()函数具有相同的随机数种子后,产生的随机数是可以预知的。
3.函数结合
floor(rand(0)*2):对伪随机数rand(0)的结果乘以2后在向下取整,每次结构都是011011
group by函数:对数据进行分组,值相同的为一组
count()函数:统计表中记录行数,返回匹配条件的行数
4.报错原理
select floor(rand(0)*2) as a,count(*) as a from testb group by (a);
floor(rand(0)*2)的值为011011,经过group by 之后再count统计,理论结果应该是2个0,4个1,但是此处却报错了
关键就是理解group by函数的工作原理
group by(key)在分组时会循环读取每一行,将结果保存在临时表中,每读取一行的key是,如果key存在于临时表中,则更新临时表中的数据(更新数据是,不在计算rand()值);如果key不在临时表中,在临时表中插入key所在行的数据(插入数据时,会重新计算rand()值)
对于语句:select floor(rand(0)*2) as a,count(*) as a from testb group by (a);
执行结果会产生011011这个序列,group by时,建立空虚拟表,然后总sql语句执行结果序列011011读取并插入数据库
key | count(*) |
---|---|
1.虚拟表写入第一条记录,执行floor(rand(0)*2),发现结果为0【此时为第一次计算】
操作 | key | floor(rand(0)*2) | count(*) |
---|---|---|---|
取第一条记录 | 0 |
2.查询虚拟表,发现0值不存在,则插入新的的键值,插入时重新计算floor(rand(0)*2),此时floor(rand(0)*2)=1,插入虚拟表,第一条记录插入完成,结果为1.
操作 | key | floor(rand(0)*2) | count(*) |
---|---|---|---|
取第一条记录 | 0 | ||
插入记录 | 1 | 1 | 1 |
3.虚拟表写入第二条记录,再次计算floor(rand(0)*2),发现结果为1【此时为第三次计算】。此时发现虚拟表中存在key=1,不会进行第四次floor(rand(0)*2)计算,只需在count(*)加1,第二条记录查询完毕。
操作 | key | floor(rand(0)*2) | count(*) |
---|---|---|---|
取第一条记录 | 0 | ||
插入记录 | 1 | 1 | 1 |
取第二条记录,不用插入 | 1 | 1 | 2 |
4.虚拟表写入第三条记录,再次计算 floor(rand(0)*2),结果为0【第4次计算】。查询虚拟表,发现没有key为0的数据记录,则插入该数据。虚拟表插入数据时,再次计算 floor(rand(0)*2)=1【此时为第五次计算】。但是,key=1这个主键已经存在于虚拟表中,而计算的需要插入的key也为1.所以产生了主键冲突错误,即:duplicate entry的报错,且报错的值为主键的值==floor(rand(0)*2)=1【第五次计算值】
操作 | key | floor(rand(0)*2) | count(*) |
---|---|---|---|
取第一条记录 | 0 | ||
插入记录 | 1 | 1 | |
取第二条记录,不用插入 | 1 | 1 | 2 |
取第三条记录 | 0 | ||
插入记录 | 1??? | 1 |
总结:
通过上述分析,虚拟表在写入第三条记录时,产生报错,此时floor(rand(0)*2)一共计算了5次。所以至少需要3条数据才会产生报错。
另外,floor(rand()*2)产生的序列是不可测的,可能会发生正常无报错的情况。最重要的是,前面几条记录查询后不能让虚拟表产生主键冲突,那之后也不会产生报错。
应用
1.获取数据版本,用户名:
Select count(*),concat(version(),floor(rand()*2),user()) as a from users group by a;
2.所有库名:
select count(*),concat((SELECT schema_name FROM information_schema.schemata limit 0,1),floor(rand(0)*2)) as x_col from information_schema.tables group by(x_col);
3.当前数据库表名等等
select count(*),concat((SELECT table_name FROM information_schema.tables where table_schema=database() limit 0,1),floor(rand(0)*2)) as x_col from information_schema.tables group