什么是序列化?
序列化(serialize)是将对象的状态信息转换为可以存储或传输的形式过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。【将状态信息保存为字符串】
简单的理解:将php中 对象、类、数组、变量、匿名函数等,转化为字符串,方便保存到数据库或文件中
什么是反序列化?
序列化就是将对象的状态信息转为字符串存储起来,那么反序列化就是再将这个状态信息拿出来使用。(重新再转化为对象或者其他的)【将字符串转化为状态信息】
当在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,保存对象的值方便之后的传递与使用。
1 | <meta charset="utf-8"> |
与serialize()对应的,unserialize()可以从已存储的表示中创建php的值,单就本次环境而言,可以从序列化后的结果中恢复对象(object)
1 | <meta charset="utf-8"> |
本质上serialize()和unserialize()在php内部实现上是没有漏洞的,漏洞的主要产生是由于应用程序在处理对象、魔术函数以及序列化相关问题的时候导致的。
当传给unserialize()的参数可控时,那么用户就可以注入精心构造的payload。当进行反序列化的时候就有可能会触发对象中的一些魔术方法,造成意想不到的危害。
php中有一类特殊的方法叫”Magic function”(魔术方法),这里我们着重关注一下几个:
1、__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。(构造函数)
2、__destruct():当对象呗销毁时会自动调用。(析构函数)
3、__wakeup():如前所提,unserialize()时会自动地调用。
1 |
|
首先来了解下“__toString()”方法,当打印一个对象时,如果定义了“__toString()”方法,就能在测试时,通过echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据。
封神台靶场:http://59.63.200.79:8010/uns/index.php?source
同时发现$s->source=__FILE__存在可控变量
尝试构造序列化对象
1 | Class readme{ |
——tostring()魔术方法,当输出一个文件对象时,将其转化为字符串输出,且显示为代码高亮;
1 | if(isset($_GET[‘source’])){ |
获取get传参中source的值(有的话),$S新生成一个对象,对象中的source属性指向为__FILE__(即目录本身、当前路径),输出$_s这个对象(当然输出时为自动调用__ToString()魔术方法)。最后有一个exit,执行这一块会退出,所以我们不能让这一块执行,所以不能有get传参。
1 | if(isset($_COOKIE[‘todos’])){ |
这一块是对cookie的检测和执行,并且有反序列化函数unserialize(),所以这一块是我们需要操作的。这一段代码首先获取传入的cookie值赋值给$c,并且cookie的前三十二位值赋值给$h,后面的赋值给$m,如果后三十二位MD5编码结果与前三十二位相同,则为$todos赋值$m作为参数反序列化执行的结果。配合下面一段代码遍历输出$todos。
1、
=$todo?> 直接输出,则会调用__toString()方法2、而在 $s->source = FILE; 存在可控变量
3、想要读取flag.php则需要满足条件md5($m) === $h
4、所以我们尝试构造 $m = serialize($todos)和$h = md5($m)满足这两个条件
1 | foreach($todos as $todo): |
再下面一段是关于POST传参的判断执行(没有反序列化函数,不能利用):
1 | if(isset($_POST[‘text’])){ |
然后我们就要利用cookie的传参来读取flag.php这一文档
1 | $c = $_COOKIE['todos']; => 我们传入的 |
;转为url编码%3b
e2d4f7dcc43ee1db7f69e76303d0105ca:1:{i:0%3bO:6:”readme”:1:{s:6:”source”%3bs:8:”flag.php”%3b}}
复制到cookie里面直接读出flag.php源码