前言
本题涉及到的知识点主要有,PHP 对象的序列化与反序列化 以及在反序列化中的 __wakeup() 函数漏洞。
PHP 对象序列化与反序列化
所有 PHP 里面的值都可以使用函数 serialize()
来返回一个包含字节流的字符串来表示。函数 unserialize()
能够把字符串重新变回 PHP 原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。为了能够 unserialize() 一个对象,这个对象的类必须已经定义过。
如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。 如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个定义该类的文件或使用函数 spl_autoload_register()
来实现。
函数 __sleep() 和 __wakeup()
这两个函数均为 PHP 中的魔术方法。serialize() 函数会检查类中是否存在一个魔术方法 __sleep()
。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。__sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。
与之相反,unserialize() 会检查是否存在一个 __wakeup()
方法。如果存在,则会先调用 __wakeup() 方法,预先准备对象需要的资源。__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
PHP反序列化漏洞
该漏洞的主要原理为,__wakeup() 函数触发于 unserilize() 函数调用之前,当序列化字符串中表示对象属性个数的值大于真实个数的属性时就会导致反序列化失败同时使得 __wakeup() 函数失效。
序列化返回的字符串格式:
O:<length>:"<class name>":<n>:{<field name 1><field value 1>...<field name n><field value n>}
O:表示被序列化的是对象,除了o,该处还可以为: a表示数组、s表示字符、i表示数字
< length>:表示序列化的类名称长度
< class name>:表示序列化的类的名称
< n >:表示被序列化的对象的属性个数
< field name 1>:属性名
< field value 1>:属性值
题目分析
进入网页,观察到题目所给代码中存在 __wakeup() 函数,同时结合本题题目为 unserialize3 可知,本题考察点应该为 PHP 反序列化及 __wakeup() 函数漏洞。
既然题目要反序列化,肯定首先要有一个序列化的字符串。由于题目代码中只给了我们一个类,并没有给出一个明显的序列化字符串,故可尝试编写程序,先将该类实例化一个对象,再将该对象序列化,进而得到该字符串。
得到的字符串如下:
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
观察题目剩余部分,可以看到题目给出提示,我们应该使用 ?code= 来进行传参。
同时我们需要利用 PHP 反序列化漏洞来绕过 __wakeup() 函数,如果不绕过 __wakeup() 方法,那么将会输出"bad requests"并退出脚本。
综上,我们应该将上述的序列化的字符串中的对象属性个数由真实值1修改为任意大于1的数,然后将其传给 code 即可得到 flag。
本题相关知识点在 PHP 手册中的描述及详解如下:
PHP:对象序列化
PHP:魔术方法