Thinkphp 6 小于 6.0.2 任意文件创建覆盖漏洞分析
2020-02-02 18:51:21 Author: mp.weixin.qq.com(查看原文) 阅读量:130 收藏

本文作者:1x2Bytes(信安之路红蓝对抗小组成员)

6.0.0 中有两个版本存在该漏洞, dev 版本只能覆盖任意位置的文件,6.0.0-1 则可以在特定的情况下控制写入的内容实现 getshell,看到一些师傅的 blog 的文章使用 composer 下载的源码, Thinkphp6 也确实开始使用 composer 的方式进行安装但是我使用 composer 方式下载的源码无法复现,猜测进行了修复,于是在网上找一键安装包,找了半天找到一个 11 月份的版本遂复现成功.`

具体漏洞位置:

vendor\topthink\framework\src\think\session\Store.php 文件254行开始

public function save(): void{        $this->clearFlashData();        $sessionId = $this->getId();        if (!empty($this->data)) {            $data = $this->serialize($this->data);            $this->handler->write($sessionId, $data);        } else {            $this->handler->delete($sessionId);        }        $this->init = false;    }

这里 $this->handler->write($sessionId, $data)是漏洞的关键位置,handler的值我们从文件开头 53 行的__construct方法中可以看到 handler 是 SessionHandlerInterface 接口

我们搜索 SessionHandlerInterface

分别发现 File 类与 Cache 类都实现了该接口, 查看了 Cache 的 write 方法,并没有进行文件写入的操作,于是分析 File 中的 write 方法,看注释应该是跟 Session 操作相关,在文件vendor\topthink\framework\src\think\session\driver\File.php 的 210 行

$filename变量是从 getFileName 方法中获取,传入的值为 $sessID, 跟进该方法,在 File 文件的 117 行

 protected function getFileName(string $name, bool $auto = false): string{        if ($this->config['prefix']) {            // 使用子目录            $name = $this->config['prefix'] . DIRECTORY_SEPARATOR . 'sess_' . $name;        } else {            $name = 'sess_' . $name;        }        $filename = $this->config['path'] . $name;        $dir      = dirname($filename);        if ($auto && !is_dir($dir)) {            try {                mkdir($dir, 0755, true);            } catch (\Exception $e) {                // 创建失败            }        }        return $filename;    }

这里判断是否有配置 session 文件的前缀,配置文件在config/session.php,如果存在配置则拼接到路径的最后并在 $name 前加上字符串sess_,不存在则直接拼接sess_前缀后返回文件名,最后 write 方法进行了 writeFile 操作,跟进 writeFile 方法,在文件 170 行进入 file_put_contents 操作,其中的文件名和内容我们都可控,我们下一步要查看如何控制我们写入的值和文件名

回到前面的 save 方法,传入的$sessionId变量是 getId 方法获取的,查看 getId 方法

该方法返回 id 的值,该值已经在 setId 方法中进行设置,于是查看 119 行的 setld 方法 

 public function setId($id = null): void{        $this->id = is_string($id) && strlen($id) === 32 ? $id : md5(microtime(true) . session_create_id());    }

这里对 $id 的值进行了判断长度是否为 32 位,所以构造 payload 的时候要注意长度为 32

查找使用 setId 方法的文件,在vendor\topthink\framework\src\think\middleware\SessionInit.php46 行

$varSessionId 变量的值从配置中获取session.var_session_id的值,因为 session.var_session_id默认是空 ,所以进入另一分支$sessionId变量的值由$request->cookie($cookieName)获取, $cookieName 由 $this->session->getName() 获取,查看 getName 方法

返回的值为 name,查看 name 变量的值在 Store 文件 36 行已经赋值,为 PHPSESSID

复现的时候要在 app/middleware.php 文件中开启即去除注释 \think\middleware\SessionInit::class然后在控制器中使用 Thinkphp 的 session 方法设定值,在 Index 控制器中修改 index 方法

   public function index(){    if($_GET['code']){        session('test', $_GET['code']);        return 'ThinkPHP V6.0.0';    }    }

搭建好后使用以下 Payload:

../../../../testgetshellvuln.php //在根目录下写入文件../../../../public/shellvuln.php //写入public

成功 getshell


文章来源: http://mp.weixin.qq.com/s?__biz=MzI5MDQ2NjExOQ==&mid=2247492184&idx=1&sn=963b89f57afa43623c84fd99bbddd992&chksm=ec1dd270db6a5b66285d8036b341d5a5dc4107e73c95a489e2dbc5e9b6d8d4d9d97276b4dc08#rd
如有侵权请联系:admin#unsafe.sh