天天看点

浅析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反序列化字符串逃逸

这说明反序列化的过程是有一定识别范围的,在这个范围之外的字符都会被忽略,不影响反序列化的正常进行。而且可以看到反序列化字符串都是以

";}

结束的,那如果把

";}

添入到需要反序列化的字符串中(除了结尾处),就能让反序列化提前闭合结束,后面的内容就相应的丢弃了。

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

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

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

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反序列化字符串逃逸

这些特点一定要清楚,否则在做题时可能就因为这些基础知识而做出不来。

字符串逃逸

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 "<br>";

$r = lemon(serialize($user));
var_dump($r);
var_dump(unserialize($r));
?>
           

正常输入的话

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

因为我们输入的是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反序列化字符串逃逸

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 '<br>';
print("name:".$fake['name'].'<br>');
print("sign:".$fake['sign'].'<br>');
print("number:".$fake['number'].'<br>');
?>
           

如果正常输入的话,回显出的结果如下:

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

已经知道number的值是固定的2020

如果想要修改这个值,就要在sign中加入

";s:6:"number";s:4:"2000";}

,其长度为27,仔细观察便可以发现是利用反序列化的第一个特点

底层代码是以

;

作为字段的分隔,以

}

作为结尾

,想要将之前的number挡在序列化之外,从而可以反序列化自己构造的,但直接输入发现是不行的,并没有将我们输入的给反序列化了

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

在实验代码中有替换功能,当遇到

lemon 或 shy

会自动替换为空,也这里用

shy

做为name的输入,故意输入敏感字符,替换为空之后来实现字符逃逸,

三个字符变成零个字符,吃掉了三个字符

,输入8个

shy

,也就是腾出了24个字符的空间,利用这个空间来进行构造,由于

";s:4:"sign";s:54:"hello成了

name的内容,所以还要在后面加个

";s:4:"sign";s:4:"eval

作为sign序列化的内容。

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