PHP反序列化链
PoP链的核心,就是魔术方法。而php的魔术方法中涉及到反序列化的大致有以下几种:
__destruct: 析构函数,会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。一般来说,也是Pop链的入口。
__toString: 类对象遇到字符串操作时触发。
__wakeup: 类实例反序列化时触发。
__call: 当调用了类对象中不存在或者不可访问的方法时触发。
__callStatic: 当调用了类对象中不可访问的静态方法时触发。
__get: 当获取了类对象中不可访问的属性时触发。
__set: 当试图向类对象中不可访问的属性赋值时触发。
__invoke: 当对象调用为函数时触发
通俗来讲,我们可以把__destruct
当作挖掘反序列化链的入口,因为__wakeup
一般内容为反序列化的限制。
从__destruct
开始,我们探讨,在不同的情况下我们分别会如何寻找调用链?
__call
function __destruct(){
$this->a();
}
我们可以直接跟下去看a方法的内容,如果当前类不存在a方法,则会优先查找父类的a方法。如果父类也不存在a方法(或是不可访问),那么就会触发当前类的__call
魔术方法。
$this->a() ==> 当前类a方法 ==> 父类a方法 ==> 当前类__call方法 ==> 父类__call方法
值得注意的是,如果触发__call
方法,那么a会作为__call
的方法的第一个参数,而参数列表会作为__call
的方法第二个参数。
而当代码出现
function __destruct(){
$this->a->b();
}
此时,我们便不用纠结于$this->a
是代表什么类了,我们可以调用任意类的b方法。换言之,我们也可以调用任意没有b方法的类对象的__call
方法。
$this->a->b() ==> 任意类的b方法 ==> 任意类的`__call`方法
__get
与__set
当代码出现
function __destruct(){
echo $this->a;
}
这时echo会优先访问当前类的a变量,然后寻找父类的a变量,如果不存在该类变量或者不可访问时,则会调用对应的__get
方法
$this->a ==> 当前类a变量 ==> 父类a变量 ==> 当前类__get方法 ==> 父类__get方法
同样,如果调用$this->a->b
,我们就有可能触发任意类的__get
方法。
而当代码出现
function __destruct(){
$this->a = 1;
}
如果当前类不存在a变量时,则会触发__set
方法
__toString
__toSring
是一个很特别的魔术方法,当类对象遇到字符串操作时触发。
他一般常见于这种代码中
function __destruct(){
echo $this->a;
}
当我们控制$this->a
时,我们就可以触发任意类的__toSring
方法。
echo $this->a ==> 任意类的__toSring
其他方法
其他方法主要包括__wakeup
和__invoke
,这两种方法比较特殊,在反序列化链中出现的概率比较小。
由于__wakeup
是在反序列化时执行,所以一般来说,开发者会倾向于在wakeup函数中加入过滤部分,以减少反序列化漏洞的危害。
而__invoke
触发条件是当尝试以调用函数的方式调用一个对象时。但可惜的是,如果你试图调用一个方法时,会优先执行__call
逻辑,而不是invoke。