关于魔术方法?

什么是魔术方法

一个预定义好的,在特定情况下自动触发的行为方法。

魔术方法的作用

反序列化漏洞的成因:
反序列化过程中,unserialize()接收的值(字符串)可控;
通过更改这个值(字符串),得到所需要的代码;
通过调用方法,触发代码执行。

常见的魔术方法

image.png

魔术方法相关机制

image.png

__construct()

构造函数,在实例化一个对象的时候,首先会去自动执行的一个方法;

<?php
class User {
    public $username;
    public function __construct($username) {
        $this->username = $username;
        echo "触发了构造函数1次" ;
    }
}
$test = new User("benben");   //实例化对象时触发构造函数_construct()
$ser = serialize($test);
unserialize($ser);

?>

触发时机:
实例化对象功能: 提前清理不必要内容
参数:非必要
返回值:

就是在 new 一个 User 实例化的时候 自动执行了 __construct 方法
然后 去调用了 username 执行了echo
并不是 $ser 去触发的
**验证 : **

image.png
这样他还是去执行了 echo
证明是实例化的时候触发了__construct()方法

__destruct()

析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法。

<?php
class User {
    public function __destruct()
    {
        echo "触发了析构函数1次"."<br />" ;
    }
}
$test = new User("benben");   //创建不触发 ,但是创建完执行下一个就触发 了 __destruct 
$ser = serialize($test);
unserialize($ser);   //反序列化之后 会触发 

?>

实例化对象结束后,代码运行完会销毁,触发析构函数_destruct()
这个代码会触发两次 __destruct() 方法
image.png

例题 : __destruct()

image.png
这个题了会执行一次 __destruct

<?php
error_reporting(0);
class User {
    var $cmd = "echo 'dazhuang666!!';" ;
    public function __destruct()
    {
        eval ($this->cmd);
    }
}
$ser = $_GET["benben"];
unserialize($ser);   // 在这里结束了反序列化之后会去调用 __destruct() 方法
 
?>

构造 payload :
image.png
这个会执行几次 __destruct 呢
答案 是两次
new 的时候不执行 ,但是当new完之后就 执行了一次 __destruct()方法
所以会出现phpinfo();
然后第二执行 是 echo 完 去调用了 __destruct();
image.png
验证 :
当注释了$a 之后还是会去执行 phpinfo()
image.png
例题payload:

O:4:“User”:1:{s:3:“cmd”;s:10:“phpinfo();”;}

image.png

__sleep()

序列化serialize()函数会检查类中是否存在一个魔术方法_sleep()。
如果存在,该方法会先被调用,然后才执行序列化操作。
此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
如果该方法未返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误。

触发时机:序列化serialize()之前
功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
参数:成员属性
返回值:需要被序列化存储的成员属性

例子:
image.png
代码分析 :

<?php
class User {
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    public function __construct($username, $nickname, $password)    {   //第一个触发 
        $this->username = $username;   //   username = a
        $this->nickname = $nickname;   //   nickname = b
        $this->password = $password;   //   password = c
    }
    public function __sleep() {    //第二次触发 
        return array('username', 'nickname');
    }
}
$user = new User('a', 'b', 'c');    //new User 传入值 a b c  这里会触发 __construct
echo serialize($user);  // 然后去输出 一个序列化的$user  ,然后回触发一个__sleep()
// 因为如果序列化前,存在__sleep()方法就会先调用__sleep()方法。
?>

先注释掉 序列化的内容 看是否先调用了 __construct 方法 ,成功赋值呢 ?
image.png
可以看出是成功调用了 __construct 方法,成功赋值了的
然后在加上 序列化
image.png
看调用了 __sleep()
只返回了 username 和 nickname 的值

例题

image.png
很简单只需要传入一个值 ,?benben = 需要执行的命令
因为实例化的时候会去调用__construct 去赋值
$cmd 等于你 要执行的命令 , 就等于 username = KaTeX parse error: Expected group after '_' at position 54: …/>但是在序列化之前会去调用 _̲_sleep() 方法 然后会…cmd 赋值给 username ,然后__sleep() 去调用 username 就执行了 system($cmd );
image.png
本地测试 :
image.png
没有序列化前之调用了__construct() 方法
image.png
成功执行了 __sleep()方法

__wakeup()

unserialize()会检查是否存在一个_wakeup()方法。
如果存在,则会先调用_wakeup()方法,预先准备对象需要的资源。预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。

触发时机:反序列化unserialize()之前
功能:
参数:
返回值:

___wakeup()在反序列化unserialize()之前
__destruct()在反序列化unserialize()之后

<?php
error_reporting(0);
class User {
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    private $order;
    public function __wakeup() {
        $this->password = $this->username;
    }
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}';  
//字符串里面并没有password 
//但是还是输出了password 
var_dump(unserialize($user_ser));
?>

输出结果:

object(User)#1 (4) { [“username”]=> string(1) “a” [“nickname”]=> string(1) “b” [“password”:“User”:private]=> string(1) “a” [“order”:“User”:private]=> NULL }

在反序列化之前触发了__wakeup 给 password赋值

例题

image.png
很简单 运行反序列化之前会去调用 __wakeup() 方法
image.png
把其他几个都去掉只留一个 username 因为本来也就只需要这一个

O:4:“User”:1:{s:8:“username”;s:2:“id”;}

image.png
成功执行id 命令

__toStrings()

表达方式错误误导魔术方法触发

触发时机:把对象被当成字符串调用
功能:
参数:
返回值:

image.png

<?php
error_reporting(0);
class User {
    var $benben = "this is test!!";
         public function __toString()
         {
             return '格式不对,输出不了!';
          }
}
$test = new User() ;
print_r($test);
echo "<br />";
echo $test;
?>

new 一个 user对象的时候
print_r($test)
是不会去调用 __toString方法的
但是去echo 的时候 就会去调用__toString方法
输出了 格式不对 ,输出不了

__invoke()

格式表达错误导致魔术方法触发

触发时机:把对象当成函数调用
功能:
参数:
返回值:

image.png

<?php
error_reporting(0);
class User {
    var $benben = "this is test!!";
         public function __invoke()
         {
             echo  '它不是个函数!';
          }
}
$test = new User() ;
echo $test ->benben;
echo "<br />";
//$test();   //这里就当成一个函数来执行了 
?>

把类User实体化并赋值给$test为对象正常输出对象里的值benben
加()是把test当成函数test()来调用,此时触发invoke()

image.png

错误调用相关魔术方法

__call()

触发时机:调用一个不存在的方法
功能:
参数:2个参数传参 a r g 1 , arg1, arg1,arg2
返回值:调用的不存在的方法的名称和参数

image.png

调用的方法abc()不存在,触发魔术方法call()
触发call(),传参 a r g 1 , arg1, arg1,arg2 ( abc,b)
$arg1,调用的不存在的方法的名称;
$arg2,调用的不存在的方法的参数;

__callStatic()

触发时机:静态调用或调用成员常量时使用的方法不存在
功能:
参数:2个参数传参 a r g 1 , arg1, arg1,arg2
返回值:调用的不存在的方法的名称和参数

<?php
error_reporting(0);
class User {
    public function __callStatic($arg1,$arg2)
    {
        echo "$arg1,$arg2[0]";
          }
}
$test = new User() ;
$test::callxxx('a');
?>

image.png

静态调用::时的方法callxxx()不存在
触发callStatic(),
传参 a r g 1 , arg1, arg1,arg2 (callxxx,a)

__get()

触发时机:调用的成员属性不存在
功能:
参数:传参$arg1
返回值:不存在的成员属性的名称

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    public $var1;
    public function __get($arg1)
    {
        echo  $arg1;
    }
}
$test = new User() ;
$test ->var2;   
?>

这里调用的var2 但是成员属性里面没有var2 ,只有var1 所以触发了 __get()方法
image.png
在尝试不触发
image.png
给 var1 赋值
然后去调用 var1
就会输出 没有触发,

__set()

触发时机:给不存在的成员属性赋值
功能:
参数:传参 a r g 1 , arg1, arg1,arg2
返回值:不存在的成员属性的名称和赋的值

image.png
就会输出 var2 ,1
和__get 差不多的意思 ,
测试存在成员变量 会成功赋值并输出
image.png

__isset()

触发时机:对不可访问属性使用isset()或empty()时,_isset()会被调用。
功能:
参数:传参$arg1
返回值:不存在的成员属性的名称

<?php
error_reporting(0);
class User {
    private $var;
    public function __isset($arg1 )
    {
        echo  $arg1;
    }
}
$test = new User() ;
isset($test->var);
?>

isset()调用的成员属性var不可访问或不存在
这里是存在的 ,但是变量是不可访问的 所以还是会返回他的var 参数值
触发 __isset()
返回$arg1 不存在成员属性的名称

__unset()

触发时机:对不可访问属性使用unset()时
功能:
参数:传参$arg1
返回值:不存在的成员属性的名称

<?php
error_reporting(0);
class User {
    private $var;
    public function __unset($arg1 )
    {
        echo  $arg1;
    }
}
$test = new User() ;
unset($test->var);
?>

var

和 __isset差不多的

使用的时候是unset 去调用 的时候不存在 或不可访问 就会触发 unset()

__clone()

触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法_clone()
功能:
参数:
返回值:

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    private $var;
    public function __clone( )
    {
        echo  "__clone test";
          }
}
$test = new User() ;
$newclass = clone($test)
?>

使用clone()克隆对象完成后,触发魔术方法_clone()
image.png
-----------------------------------------------------------------------------------------------------------------
image.png
image.png
-------------------------------------------------------------------------------------------------------------------

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值