0x00 序列化和反序列化
简单的理解:序列化就是使用serialize()将对象的用字符串的方式进行表示,反序列化是使用unserialize()将序列化的字符串,构造成相应的对象,反序列化是序列化的逆过程。 序列化的对象可以是class也可以是Array,string等其他对象。
0x01 对象序列化和反序列化的功能作用
1. 对象序列化的功能作用
概念:对象是在内存中存储的数据类型,寿命通常随着生成该对象的程序的终止而终止,但是有些情况下需要将对象的状态保存下来,然后在需要使用的时候将对象恢复,对象状态的保存操作就是对象序列化的过程。对象序列化就是将对象转化为2进制字符串进行保存。
作用:将对象的状态通过数值和字符记录下来,以某种存储形式使自定义对象持久化,方便需要时候将对象进行恢复使用,用于对象的传递以及使程序代码更具维护性
语法:在创建对象class后使用serialize()函数将声明的对象的某个状态转化为字符串然后进行保存或传递。
示例代码: class serialize code:
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:
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:
output:
0x02 反序列化存在的问题
问题原因:漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码或变量用户可控,就可能产生反序列化漏洞,根据反序列化后不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。
魔术方法:PHP的类中可能会包含一些特殊的函数叫魔术函数,魔术函数命名是以符号__开头的;
有以下的魔术方法: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set(), _state(), __clone(), __debugInfo() ...
反序列化漏洞中常见到有一些魔术方法:
__construct():在对象创建时自动被调用; __destruct():在脚本运行结束时自动被调用;__sleep():在对象序列化的时候自动被调用;__wakeup():在反序列化为对象时自动被调用;__toString(): 直接输出对象引用时自动被调用;
0x03 魔术方法的触发
构造方法 __construct()
构造方法是类中的一个特殊方法。当使用 new 操作符创建一个类的实例时,构造方法将会自动调用,其名称必须是 __construct()。在一个类中只能声明一个构造方法,而是只有在每次创建对象的时候都会去调用一次构造方法,不能主动的调用这个方法,所以通常用它执行一些有用的初始化任务。该方法无返回值。
语法: function __construct(arg1,arg2,…) { …… }
[example]:__construct() code:
output: __construct is work
析构方法__destruct()允许在销毁一个类之前执行执行析构方法,与构造方法对应的就是析构方法,析构方法允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集,程序运行结束等。析构函数不能带有任何参数,其名称必须是 __destruct()。
语法: function __destruct() { …… }
[example]:__destruct code:
output: 先延迟5s,等待代码执行结束 ,再打印 __destruct is work。
__sleep()方法是在一个类的实例被序列化了的时候调用,_wakeup()是在反序列化时被调用。__sleep()必须返回一个数组或者对象,而一般返回的是当前对象$this。返回的值将会被用来做序列化的值。如果不返回这个值,自然表示序列化失败。同时也会连累到反序列化时不会调用__wakeup()方法。
[example]:__sleep()``__wakeup()code:
output: __sleep is work __wakeup is work
如果我们想打印出一个对象,就需要调用__toString()这个魔术方法了,该方法会在直接输出对象引用时自动被调用,此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误 参考:http://php.net/__toString
[example]:__toString()code:
output: __toString is work
其他方法的介绍参考:
http://www.5idev.com/p-php_member_overloading.shtml http://php.net/__toString
0x04 一道CTF中反序列化例题
2016xctf的反序列化题目
index.php的源码:
class.php的源码:
源码分析:
首先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
0x05 小总结
本篇仅进行了部分魔术方法的总结,还有一些魔术方法后续将逐步补充,例题仅收集了1道,小伙伴们有其他例题也可提出,小编将在后续篇章继续总结。从ctf题目中体会反序列化漏洞的形成原因和利用方法是个不错的方式,期待大家的多多交流。