反序列化篇之pop链的构造(上)

pop链的构造(上篇)

php反序列化漏洞,是web安全漏洞中非常重要的一种,无论是在ctf中还是实战中都有很广泛的应用,而一般在一些大型ctf比赛中,反序列化甚至是web题中的签到题,所以说把这基础打扎实就很重要;而在php反序列化中,构造pop链肯定是一个绕不开的话题,接下来我们来介绍构造pop链的思路和过程,上篇我们先来详细介绍魔术方法,然后下篇通过多个案例介绍利用pop链将魔术方法串起来

魔术方法测试

我理解的pop链,就是用一条链子将一堆不同类中的魔术方法串起来,让没有关系的类扯上关系,由前一个魔术方法调用后一个魔术方法,连成一条链子,最终达成我们的目的,所以说了解每个魔术方法的调用条件就非常重要了,接下来我们来做一些测试:

一.__construct()__destruct()

__construct:当对象创建时会自动调用,注意是创建的时候,也就是说有new的时候就会调用,在unserialize时是不会被自动调用的

__destruct():当对象被销毁时会自动调用;当新对象创建后,它后面一定会被自动销毁,也就是调用__construct后一定会调用__destruct;或者我们直接传入一个对象,它后面被销毁时也会调用__destruct

image.png

可以看到,创建对象a时调用了__construct,然后输出序列化后的对象a,最后在销毁对象a时调用了__destruct

image.png

再看这个,我们没有创建对象,而是直接传入了一个对象,所以说它没有调用__construct却调用了__destruct

image.png

最后看这个,因为我们既创建了对象a,也传入了对象,所以说它__destruct被调用了两次,因为它两个对象最后都会被销毁

二.__sleep()__wakeup()

__sleep() :在对象被序列化之前被调用,就是说看到serialize时就会被调用,而且是先调用后再执行序列化

__wakeup(): 将在字符串被反序列化之后被立即调用,就是说看到unserialize后就会被立即调用

image.png

从上面这个例子我们可以看出来,在看到serialize($a)后,它是先调用了__sleep()魔法函数,然后才执行了echo,输出了字符串

image.png

而这个例子我们是直接输入了字符串,当它执行了unserialize转换成对象后,就会最先调用__wakeup(),它的优先级最高

三.__toString()

__toString()魔术方法是最为最要的,在构造pop链中它往往是很关键的一环,在很多种情况下都会被调用,主要是下面这些:

(1)echo($obj)或print($obj)打印对象时会触发
(2)反序列化对象与字符串连接时
(3)反序列化对象参与格式化字符串时
(4)反序列化对象字符串进行**==比较时(多为preg_match正则匹配**),因为php进行弱比较时会转换参数类型,相当于都转换成字符串进行比较
(5)反序列化对象参与格式化sql语句时,绑定参数时(用的少)
(6)反序列化对象经过php字符串函数时,如strlen(),addslashes()时(用的少)
(7)在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有tostring返回的字符串的时候tostring会被调用
(8)反序列化的对象作为class_exists()的参数的时候(用的少)

通过看它被调用的情况,不难总结出,当对象被当成了字符串的时候,__toString()就会被调用,无论是将对象打印出来,还是将对象去与字符串进行比较,它都会被调用;这里要注意的是,必须要操作的是对象的时候,才会被调用,看代码应该会清晰一些:

image.png

比如说上面这段代码,我们新建了对象a就直接打印它,照理说肯定是不会有任何回显的,因为只有字符串能被打印,对象肯定是不能被直接打印的,需要先将它序列化成字符串后才可以打印;但我们这直接打印发现它居然有输出,就是因为它按照操作字符串的方法去操作了对象,所以说调用了__toString(),然后将它的返回值输出了出来,下面再来看一个与字符串连接的例子:

image.png

也是同样的道理,变量a被当成了字符串,然后与b完成了连接,更多调用它的方法在下篇实战中演示

四.__invoke()

__invoke:当尝试以调用函数的方式调用一个对象时,__invoke()方法会被自动调用,而调用函数的方式就是在后面加上(),当我们看到像return $function();这种语句时,就应该意识到后面可能会调用__invoke(),下图是直接在对象后面加()调用

需要注意的是,这个魔术方法只在PHP 5.3.0 及以上版本有效

image.png

五.__get()__set()

__get():从不可访问的属性中读取数据,或者说是调用一个类及其父类方法中未定义属性时

__set():当给一个未定义的属性赋值时,或者修改一个不能被修改的属性时(private protected)(用的不多)

image.png

比如说看上面这个例子,echo语句调用了__toString(),然后它返回的是当前对象的a属性,但我们是没有定义a这个属性的,所以说会调用__get(),然后将返回值通过echo打印了出来

六.__call()__callStatic()

__call:在对象中调用类中不存在的方法时,或者是不可访问方法时被调用

__callStatic:在静态上下文中调用一个不可访问静态方法时被调用(用的不多)

image.png

比如说像上面这段代码,我们调用对象a中的方法c,但因为类中没有方法c,所以说就调用了__call()

七.其它魔术方法

差不多比较重要的魔术方法上面都介绍完了,下面再把其它的魔术方法简单介绍一下,这些基本上ctf中都碰不到,了解了解就行:

1
2
3
4
5
6
__isset():当对不可访问属性调用isset()或empty()时调用
__unset():当对不可访问属性调用unset()时被调用。
__set_state():调用var_export()导出类时,此静态方法会被调用。
__clone():当对象复制完成时调用
__autoload():尝试加载未定义的类
__debugInfo():打印所需调试信息

如果想要测试的可以参考php官方手册:https://www.php.net/manual/zh/language.oop5.magic.php#object.invoke

总结

在这里魔术方法就介绍完了,熟练掌握魔术方法是构造pop链的基础,然后pop链具体构造的过程和思路请看下一篇文章