什么是魔术方法
一个预定义好的,在特定情况下自动触发的行为方法。
魔术方法的作用
反序列化漏洞的成因:
反序列化过程中,unserialize()接收的值(字符串)可控;
通过更改这个值(字符串),得到所需要的代码;
通过调用方法,触发代码执行。
常见的魔术方法
魔术方法相关机制
__construct()
构造函数,在实例化一个对象的时候,首先会去自动执行的一个方法;
1 |
|
触发时机:
实例化对象功能: 提前清理不必要内容
参数:非必要
返回值:
就是在 new 一个 User 实例化的时候 自动执行了 __construct 方法
然后 去调用了 username 执行了echo
并不是 $ser 去触发的
**验证 : **
这样他还是去执行了 echo
证明是实例化的时候触发了__construct()方法
__destruct()
析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法。
1 |
|
实例化对象结束后,代码运行完会销毁,触发析构函数_destruct()
这个代码会触发两次 __destruct() 方法
例题 : __destruct()
这个题了会执行一次 __destruct
1 |
|
构造 payload :
这个会执行几次 __destruct 呢
答案 是两次
new 的时候不执行 ,但是当new完之后就 执行了一次 __destruct()方法
所以会出现phpinfo();
然后第二执行 是 echo 完 去调用了 __destruct();
验证 :
当注释了$a 之后还是会去执行 phpinfo()
例题payload:
O:4:”User”:1:{s:3:”cmd”;s:10:”phpinfo();”;}
__sleep()
序列化serialize()函数会检查类中是否存在一个魔术方法_sleep()。
如果存在,该方法会先被调用,然后才执行序列化操作。
此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
如果该方法未返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误。
触发时机:序列化serialize()之前
功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
参数:成员属性
返回值:需要被序列化存储的成员属性
例子:
代码分析 :
1 |
|
先注释掉 序列化的内容 看是否先调用了 __construct 方法 ,成功赋值呢 ?
可以看出是成功调用了 __construct 方法,成功赋值了的
然后在加上 序列化
看调用了 __sleep()
只返回了 username 和 nickname 的值
例题
很简单只需要传入一个值 ,?benben = 需要执行的命令
因为实例化的时候会去调用__construct 去赋值
$cmd 等于你 要执行的命令 , 就等于 username = $cmd ;nickname= b ; password = c
但是在序列化之前会去调用 __sleep() 方法 然后会吧传入的$cmd 赋值给 username ,然后__sleep() 去调用 username 就执行了 system($cmd );
本地测试 :
没有序列化前之调用了__construct() 方法
成功执行了 __sleep()方法
__wakeup()
unserialize()会检查是否存在一个_wakeup()方法。
如果存在,则会先调用_wakeup()方法,预先准备对象需要的资源。预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。
触发时机:反序列化unserialize()之前
功能:
参数:
返回值:
___wakeup()在反序列化unserialize()之前
__destruct()在反序列化unserialize()之后
1 |
|
输出结果:
object(User)#1 (4) { [“username”]=> string(1) “a” [“nickname”]=> string(1) “b” [“password”:”User”:private]=> string(1) “a” [“order”:”User”:private]=> NULL }
在反序列化之前触发了__wakeup 给 password赋值
例题
很简单 运行反序列化之前会去调用 __wakeup() 方法
把其他几个都去掉只留一个 username 因为本来也就只需要这一个
O:4:”User”:1:{s:8:”username”;s:2:”id”;}
成功执行id 命令
__toStrings()
表达方式错误误导魔术方法触发
触发时机:把对象被当成字符串调用
功能:
参数:
返回值:
1 |
|
new 一个 user对象的时候
print_r($test)
是不会去调用 __toString方法的
但是去echo 的时候 就会去调用__toString方法
输出了 格式不对 ,输出不了
__invoke()
格式表达错误导致魔术方法触发
触发时机:把对象当成函数调用
功能:
参数:
返回值:
1 |
|
把类User实体化并赋值给$test为对象正常输出对象里的值benben
加()是把test当成函数test()来调用,此时触发invoke()
错误调用相关魔术方法
__call()
触发时机:调用一个不存在的方法
功能:
参数:2个参数传参$arg1,$arg2
返回值:调用的不存在的方法的名称和参数
调用的方法abc()不存在,触发魔术方法call()
触发call(),传参$arg1,$arg2 ( abc,b)
$arg1,调用的不存在的方法的名称;
$arg2,调用的不存在的方法的参数;
__callStatic()
触发时机:静态调用或调用成员常量时使用的方法不存在
功能:
参数:2个参数传参$arg1,$arg2
返回值:调用的不存在的方法的名称和参数
1 |
|
静态调用::时的方法callxxx()不存在
触发callStatic(),
传参$arg1,$arg2 (callxxx,a)
__get()
触发时机:调用的成员属性不存在
功能:
参数:传参$arg1
返回值:不存在的成员属性的名称
1 |
|
这里调用的var2 但是成员属性里面没有var2 ,只有var1 所以触发了 __get()方法
在尝试不触发
给 var1 赋值
然后去调用 var1
就会输出 没有触发,
__set()
触发时机:给不存在的成员属性赋值
功能:
参数:传参$arg1,$arg2
返回值:不存在的成员属性的名称和赋的值
就会输出 var2 ,1
和__get 差不多的意思 ,
测试存在成员变量 会成功赋值并输出
__isset()
触发时机:对不可访问属性使用isset()或empty()时,_isset()会被调用。
功能:
参数:传参$arg1
返回值:不存在的成员属性的名称
1 |
|
isset()调用的成员属性var不可访问或不存在
这里是存在的 ,但是变量是不可访问的 所以还是会返回他的var 参数值
触发 __isset()
返回$arg1 不存在成员属性的名称
__unset()
触发时机:对不可访问属性使用unset()时
功能:
参数:传参$arg1
返回值:不存在的成员属性的名称
1 |
|
和 __isset差不多的
使用的时候是unset 去调用 的时候不存在 或不可访问 就会触发 unset()
__clone()
触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法_clone()
功能:
参数:
返回值:
1 |
|
使用clone()克隆对象完成后,触发魔术方法_clone()
—————————————————————————————————————–
——————————————————————————————————————-