BUUCTF-Web-[安洵杯 2019]easy_serialize_php

考点:代码审计、反序列化、反序列化中的字符串逃逸

参考了讲解字符串逃逸的一篇博客:https://blog.csdn.net/qq_45521281/article/details/107135706

一个代码审计题目,可以判断出是关于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){		//重置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));		//把序列化后的$_SESSION用filter函数过滤
// 这里可以总结经验,对可控的序列化字符串做了过滤,导致其长度k减短,就有可能会存在字符串逃逸
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']));
}

首先我们看一下反序列化的一个流程。

第3行代码接受了一个名为f的GET传参并赋值给了$function

这个function是一个关键的变量,我们可以看到17行的session['function']的值也是这个变量的值。

顺着往下找,31行是一个序列化的点,将整个session序列化之后交由filter函数处理,然后赋值给$serialize_info

这里本地var_dump看一下这个serialize_info的内容大概是什么样的

最后在38行将这个序列化字符串进行反序列化。

也就是说先序列化$_SESSION,然后再经过一个过滤函数,再反序列化出来。

那么我们捋清了反序列化的链,再需要考虑一下,如何利用反序列化得到flag。

我到这一开始没啥思路了,但是看了看36行提示phpinfo里有提示,所以传参?f=phpinfo看一下信息

发现了一个自动加载的文件,猜测这个可能是存放flag的文件,毕竟题目中也没有提示别的文件了,

然后大概思路就是传参?f=show_image,然后下面39行有个file_get_contents函数来读取文件,也就是说我们只要让其中的$_userinfo['img']的值为这个文件的base64编码,即可读取到这个文件的内容。

但是我们不能去指定img,否则会被sha1加密,就不能被39行的base64_decode解码了。

所以这里需要利用序列化去指定读取文件的路径,由于序列化字符串被filter函数过滤过,某些敏感字符替换为空了,所以长度可能减短,这就为我们进行字符串逃逸提供了条件。

反序列化字符串逃逸的原理:
在构造键值的时候某些关键字被过滤掉了,但序列化后的字符串记录的长度不会因为过滤而改变,所以就会把序列化后的字符串的结构当做值的内容给读取。
如果我们自己构造出反序列化字符串的结构,并因为过滤破坏掉原来的结构,就可以构造出恶意代码。

个人理解,跟其他注入攻击类似,本质上就是对原有序列化字符串的结构破坏,然后拼接进恶意代码,且由于反序列化的特性,在满足格式要求之后会丢弃后面多余的内容(如图,s:28之后的28个字符会被吞掉当作s:28对应的值,后面花括号外的就会被丢弃)

这样就起到了其他注入攻击中注释的作用。

我们可以控制的是session变量中的user和function,所以我们构造payload让user把function的内容吞掉,这里注意根据我们之前本地测试得到的字符串,里面要有三个元素,所以我们还需要再添加一个元素来补上:

POST传参:_SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}

flag和php是敏感字符,在使用的时候被过滤掉了,但序列化记录的字符串长度没有变化,所以在序列化的时候会继续向后读取后面的内容,那么后面的23个字符就被user吞掉当作了原来的value

这样我们就自己构造了img的值,读取了指定的文件,然后后面补上一个元素并闭合字符串,由于反序列化到到这里就满足了三个元素的判断,且存在正常的;}去闭合,所以后面原本的img元素就会被丢弃,达到了覆盖img的效果

下面继续读这个文件

拿到flag。

posted @ 2023-10-13 11:26  M0urn  阅读(60)  评论(0编辑  收藏  举报