浅析php反序列化字符串逃逸

前言:

php反序列化字符串逃逸之前没有详细的学习过,所以遇到题目看的有点懵,这次好好学习一下。

反序列化的特点

首先要了解一下反序列化的一些特点:

  1. php在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾,并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 。
class A{
     
    public $name='shy';
    public $pass='123456';
}

$lemon = new A();
echo serialize($lemon);
#反序列化后的结果为:
O:1:"A":2:{
     s:4:"name";s:3:"shy";s:4:"pass";s:6:"123456";}

超出的部分并不会被反序列化成功,如下图:
浅析php反序列化字符串逃逸_第1张图片
这说明反序列化的过程是有一定识别范围的,在这个范围之外的字符都会被忽略,不影响反序列化的正常进行。而且可以看到反序列化字符串都是以";}结束的,那如果把";}添入到需要反序列化的字符串中(除了结尾处),就能让反序列化提前闭合结束,后面的内容就相应的丢弃了。

2. 长度不对应的时候会报错

在反序列化的时候php会根据s所指定的字符长度去读取后边的字符。如果指定的长度错误则反序列化就会失败
在这里插入图片描述

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

<?php
$str='O:1:"A":3:{s:4:"name";s:3:"shy";s:4:"pass";s:6:"123456";s:5:"pass2";s:6:"123456";}';
var_dump(unserialize($str));

浅析php反序列化字符串逃逸_第2张图片
这些特点一定要清楚,否则在做题时可能就因为这些基础知识而做出不来。

字符串逃逸

0x0:特点

这类CTF题目的本质是因为改变序列化字符串的长度,从而导致反序列化漏洞。

具体的话大致都是因为php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化。而且总是先进行序列化,再进行替换修改操作。

0x01:过滤后字符变多

实验代码:

#参考字节脉搏实验室
<?php
function lemon($string){
     
	$lemon = '/p/i';
	return preg_replace($lemon,'ww',$string);
}
$username = $_GET['a'];
$age = '20';
$user = array($username,$age);
var_dump(serialize($user));
echo "
"
; $r = lemon(serialize($user)); var_dump($r); var_dump(unserialize($r)); ?>

正常输入的话
浅析php反序列化字符串逃逸_第3张图片

因为我们输入的是apple,含有两个p,所以会被替换成四个w,但是发现长度并没有变化,因此根据反序列化的特点,指定的长度错误则反序列化就会失败。

但是正是因为存在这个过滤,我们便可以去修改age的值,首先来看一下,原来序列化后";i:1;s:2:"20";}长度为16,我们已经知道了当输入一个p会替换成ww,所以如果输入16个p,那么会生成32个的w,所以如果我们输入16个p再加上构造的相同位数的";i:1;s:2:"30";},恰好是32位,即

32 pppppppppppppppp";i:1;s:2:"30";}
经过替换后
32 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww

所以非常明显了,在过滤后的序列化时会被32个w全部填充,从而使构造的代码 ";i:1;s:2:"30";} 成功逃逸,修改了age的值,而原来的那";i:1;s:2:"20";}则被忽略了因为反序列化字符串都是以";}结束的,我们传入的";i:1;s:2:"30";}已经在前面成功闭合了
浅析php反序列化字符串逃逸_第4张图片

0x02:过滤后字符变少

搭建一个简单的实验环境代码如下:

#参考Mr. Anonymous师傅的代码学习
<?php
function str_rep($string){
     
	return preg_replace( '/lemon|shy/','', $string);
}

$test['name'] = $_GET['name'];
$test['sign'] = $_GET['sign']; 
$test['number'] = '2020';
$temp = str_rep(serialize($test));
printf($temp);
$fake = unserialize($temp);
echo '
'
; print("name:".$fake['name'].'
'
); print("sign:".$fake['sign'].'
'
); print("number:".$fake['number'].'
'
); ?>

如果正常输入的话,回显出的结果如下:
浅析php反序列化字符串逃逸_第5张图片
已经知道number的值是固定的2020

如果想要修改这个值,就要在sign中加入";s:6:"number";s:4:"2000";},其长度为27,仔细观察便可以发现是利用反序列化的第一个特点底层代码是以;作为字段的分隔,以}作为结尾,想要将之前的number挡在序列化之外,从而可以反序列化自己构造的,但直接输入发现是不行的,并没有将我们输入的给反序列化了

浅析php反序列化字符串逃逸_第6张图片
在实验代码中有替换功能,当遇到lemon 或 shy会自动替换为空,也这里用shy做为name的输入,故意输入敏感字符,替换为空之后来实现字符逃逸,三个字符变成零个字符,吃掉了三个字符,输入8个shy,也就是腾出了24个字符的空间,利用这个空间来进行构造,由于";s:4:"sign";s:54:"hello成了name的内容,所以还要在后面加个";s:4:"sign";s:4:"eval作为sign序列化的内容。

浅析php反序列化字符串逃逸_第7张图片
这个构造其实也很简单,因为经过测试发现,";s:4:"sign";s:这个长度其实是不变的,变的是我们在参数sign输入的参数,这里假设输入9个shy,那么吃掉了27个字符,对应的就需要添加27个字符,目前";s:4:"sign";s:这个长度为15,所以还差12个,因为整个payload肯定是不超过100个字符的,所以加上后面的长度,也就是";s:4:"sign";s:xx:",这个长度为19,因此我们要输入的字符只需8个即可

payload:

http://127.0.0.1/1.php
?name=shyshyshyshyshyshyshyshyshy
&sign=hello123";s:4:"sign";s:4:"eval";s:6:"number";s:4:"2000";}

在这里插入图片描述
这样便可以将number的值给更改了,原理的话就是这样,做题时还要多动手进行测试。

你可能感兴趣的:(web,php)