Thinkphp cache缓存函数远程代码执行漏洞

前言

环境创建:

composer create-project  topthink/think=5.0.10 thinkphp5.0.10

    "require": {
    
    
        "php": ">=5.4.0",
        "topthink/framework": "5.0.10"
    },
composer update

影响版本:3.2.3-5.0.10
index控制器里写个cache()函数:

    public function cache()
    {
    
    
        Cache::set("name",input("get.username"));
        return 'Cache success';
    }

在复现的利用过程中我这里不能直接访问cache目录,因此写入了恶意代码但是访问不到php。但是在真实环境中,服务器如果使用了缓存而且cache目录可以直接访问的话,就可以写入恶意代码,然后直接访问利用。

分析

payload:

?username=%0d%0aphpinfo();//

跟进一下set()方法:
在这里插入图片描述
先跟进init()方法,看看初始化。
在这里插入图片描述
cache的配置在config.php中,cache.type这里是File,$connect相当于获得了config.php中关于缓存的配置,然后返回。
在这里插入图片描述
在这里插入图片描述
init()之后,跟进self::init()->set()方法:

    /**
     * 写入缓存
     * @access public
     * @param string    $name 缓存变量名
     * @param mixed     $value  存储数据
     * @param int       $expire  有效时间 0为永久
     * @return boolean
     */
    public function set($name, $value, $expire = null)
    {
    
    
        if (is_null($expire)) {
    
    
            $expire = $this->options['expire'];
        }
        $filename = $this->getCacheKey($name);
        if ($this->tag && !is_file($filename)) {
    
    
            $first = true;
        }
        $data = serialize($value);
        if ($this->options['data_compress'] && function_exists('gzcompress')) {
    
    
            //数据压缩
            $data = gzcompress($data, 3);
        }
        $data   = "<?php\n//" . sprintf('%012d', $expire) . $data . "\n?>";
        $result = file_put_contents($filename, $data);
        if ($result) {
    
    
            isset($first) && $this->setTagItem($filename);
            clearstatcache();
            return true;
        } else {
    
    
            return false;
        }
    }

文件名是由getCacheKey()方法得到的,跟进看一下:

    /**
     * 取得变量的存储文件名
     * @access protected
     * @param string $name 缓存变量名
     * @return string
     */
    protected function getCacheKey($name)
    {
    
    
        $name = md5($name);
        if ($this->options['cache_subdir']) {
    
    
            // 使用子目录
            $name = substr($name, 0, 2) . DS . substr($name, 2);
        }
        if ($this->options['prefix']) {
    
    
            $name = $this->options['prefix'] . DS . $name;
        }
        $filename = $this->options['path'] . $name . '.php';
        $dir      = dirname($filename);
        if (!is_dir($dir)) {
    
    
            mkdir($dir, 0755, true);
        }
        return $filename;
    }

先对name进行md5,默认cache_subdir是true,所以会使用子目录。这里$name"name",md5后在substr进行拼接,得到这个:

b0\68931cc450442b63f5b3d276ea4297

之后再加上路径,最终缓存文件路径是这样:

D:\phpstudy_pro\WWW\thinkphp5.0.10\runtime\cache\b0\68931cc450442b63f5b3d276ea4297.php

然后对我们get传入的cache值进行序列化:$data = serialize($value);,接着就是最关键的两行代码:

$data   = "<?php\n//" . sprintf('%012d', $expire) . $data . "\n?>";
$result = file_put_contents($filename, $data);

将序列化后的值拼接进字符串中,然后写入缓存文件。正常的写入字符串是这样:
在这里插入图片描述
前面被//注释了,可以利用换行来绕过:

?username=%0d%0aphpinfo();//

在这里插入图片描述
成功写入恶意代码。在真实的环境中如果存在文件包含或者cache目录可以直接访问,再猜测一下传入的name,就可以实现RCE了。

修复

在这里插入图片描述
在这里插入图片描述

在拼接$data前使用了exit(),使得写入恶意代码也无法执行。

猜你喜欢

转载自blog.csdn.net/rfrder/article/details/114599310