PHP反序列化--字符逃逸

参考博客文章:
https://blog.csdn.net/qq_45521281/article/details/107135706
https://www.cnblogs.com/NPFS/p/13338789.html
https://lexsd6.github.io/2020/04/26/关于php反序列化字符逃逸的思考/
https://www.php.cn/php-weizijiaocheng-481241.html

基础部分

在学习字符逃逸漏洞之前,首先需要了解php反序列化的一些特性:

1.PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且根据长度判断内容

2.当长度不对应的时候会出现报错,这是由于长度不对应导致将后面的字符当作了元素对应的值,整体格式会出现错误,导致报错。

3.可以反序列化类中不存在的元素。

在PHP的反序列化中,字符逃逸的产生是由于序列化字符串中某些内容被过滤或替换导致其元素内容发生改变但是描述长度的数字却并未改变,后面原本为元素内容的字符串逃逸出来成为了元素并成功进行了反序列化。

个人理解,本质上跟其他注入攻击类似,就是对原有序列化字符串的结构进行闭合破坏,然后拼接进恶意代码。

总而言之,字符逃逸的过程大概就是:

产生序列化字符串 → 序列化字符串经过过滤 → 进行反序列化

下面我们来具体的分析。

过滤后字符变少的情况

先说过滤后字符变少的情况主要是因为我最先做的这个题目,目前理解的也算是比较清楚。

这里默认读者都已经有了反序列化的基础,我们直接写代码做个实验看看过滤后字符串变少的一个情况

<?php
function filter($str){		// 定义一个过滤函数,把符合条件的内容置换为空
    $filter_arr = array('php','flag');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$str);
}
$test['name'] = $_GET['name'];
$test['sign'] = $_GET['sign'];

$r = filter(serialize($test));	// 序列化这个数组并对生成的序列化字符串进行处理
echo $r;
echo "</br>";

$untest = unserialize($r);		// 将处理过的序列化字符串反序列化出来
echo $untest['name']."</br>";
echo $untest['sign']."</br>";

var_dump(unserialize($r));
?>

这是未被过滤的正常输入产生的序列化字符串和反序列化结果

如果我们输入一个敏感字符串

这里就会发现我们输入的name参数值被过滤掉了,所以红框中的7个字符就会被当作name序列化后的值,但是这样就会导致序列化字符串的格式错误,所以后面的反序列化无法正常执行。

那么这个时候我们可以干什么,因为原来sign的序列化字符串被name给吞掉了,所以我们就可以通过引号闭合前面的值然后构造后面的序列化字符串,最后闭合。我们只要确保name正好吞到新的值前面即可。

这里的长度为19(注意我圈出来的地方,我们需要吞的就是这些字符),我们让前面被过滤掉19个字符即可全部吞掉这些字符串,后面就可以构造新的sign值

Taoyi_test.php?name=phpphpphpphpphpflag&sign=";s:4:"sign";s:13:"HackedByM0urn";}

可以看到sign值已经被替换成了我们指定的值了。

下面看一道真题

BUUCTF-Web-[安洵杯 2019]easy_serialize_php:https://www.cnblogs.com/M0urn/articles/17761242.html

过滤后字符变多的情况

这种情况其实个人感觉是相对前者来说简单的,还是先来写个代码实践看一下,实践出真知

<?php
function filter($str){		// 定义一个过滤函数,把符合条件的内容置换为多一个字符
    $filter_arr = array('hi','flag');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'hhi',$str);
}
$test['name'] = $_GET['name'];
$test['sign'] = $_GET['sign'];

$r = filter(serialize($test));	// 序列化这个数组并对生成的序列化字符串进行处理
echo $r;
echo "</br>";

$untest = unserialize($r);		// 将处理过的序列化字符串反序列化出来
echo $untest['name']."</br>";
echo $untest['sign']."</br>";

var_dump(unserialize($r));
?>

同样我们先看看正常的

来试试敏感字符

可以看到因为超出限制而报错了,原因是这里只会读取到4位,后面的字符就逃逸掉了,造成了之后的序列化中,多出来的这些字符抢占了本属于hi";s:4:"sign";s:4:"1111";}的位子,使其溢出,而我们要做的就是使溢出的这部分在闭合前一字符串的同时,符合php反序列化规则,能够被成功反序列化,而后面的多余字符就被丢弃了。

可以看到sign的值被改掉了。我们可以先写后面的注入内容,然后计算需要多少个字符的位置,再在前面让其溢出对应数量的字符。

BUUCTF-Web-[0CTF 2016]piapiapia:https://www.cnblogs.com/M0urn/articles/17761239.html

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