wywwzjj's Blog

ThinkPHP 6.0 任意文件写入

字数统计: 682阅读时长: 3 min
2020/01/17 Share

概述

2020 年 1 月 10 日,ThinkPHP 团队发布一个补丁更新,修复了一处由不安全的 SessionId 导致的任意文件操作漏洞。

该漏洞允许攻击者在目标环境启用 session 的条件下创建任意文件以及删除任意文件,在特定情况下可 getshell。

具体受影响版本为 ThinkPHP 6.0.0 - 6.0.1。

环境搭建

composer 创建项目。

composer create-project --prefer-dist topthink/think=6.0.0 thinkphp6.0.0

在 app/controller/Index.php 中加一行代码,使 session 内容可控,方便漏洞复现。

class Index extends BaseController {
public function index() {
session('test', input('j'));
}

PS:TP 6 默认没开启 session,手动开下,在 app/middleware.php 取消注释即可。

<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class
];

复现

image.png

image.png

image.png

分析

https://github.com/top-think/framework/commit/1bbe75019ce6c8e0101a6ef73706217e406439f2

如果传入的 $id 长度为 32 即可控。TP 6.0.2 加了个条件,用 ctype_alnum 检测了下 ​$id,只能是字母或数字。

# src/think/session/Store.php
$this->id = is_string($id) && strlen($id) === 32 ? $id : md5(microtime(true) . session_create_id());

handle 函数将 cookie 中的 PHPSESSID 对应的值设为 sessionId。

// middleware/SessionInit.php
public function handle($request, Closure $next) {
// Session初始化
$varSessionId = $this->app->config->get('session.var_session_id');
$cookieName = $this->session->getName();

if ($varSessionId && $request->request($varSessionId)) {
$sessionId = $request->request($varSessionId);
} else {
$sessionId = $request->cookie($cookieName);
}

if ($sessionId) {
$this->session->setId($sessionId);
}

image.png

剩下的文件处理其实就是 session 本身的处理了,比如 $_SESSION 数组被序列化后写入文件保存以及清除。

// src/think/session/Store.php
/**
* 保存session数据
* @access public
* @return void
*/
public function save(): void {
$this->clearFlashData();
$sessionId = $this->getId();

if (!empty($this->data)) {
$data = $this->serialize($this->data);
$this->handler->write($sessionId, $data);
} else {
// data 为空就进行删除
$this->handler->delete($sessionId);
}

$this->init = false;
}

跟进 $this->handler->write($sessionId, $data); 的具体实现。

// session/driver/File.php
public function write(string $sessID, string $sessData): bool {
$filename = $this->getFileName($sessID, true);
$data = $sessData;

if ($this->config['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}

return $this->writeFile($filename, $data);
}

// 这里就落实到 file_put_contents() 了
protected function writeFile($path, $content): bool {
return (bool) file_put_contents($path, $content, LOCK_EX);
}

总结

总的来说还是比较鸡肋,需要能控制 session,直接打不了。

所以要与具体的业务结合,寻找 session 的输入点,比如某些系统将用户名直接存入 session 中。

另外,那个删除点就更难控制了,那也是 TP 清除 session 的正常功能,所以能删的文件必须以 sess_ 开头。

参考

https://mp.weixin.qq.com/s/UPu6cE20l24T6fkYOlSUJw

https://mochazz.github.io/2020/01/14/ThinkPHP6.0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99

CATALOG
  1. 1. 概述
  2. 2. 环境搭建
  3. 3. 复现
  4. 4. 分析
  5. 5. 总结
  6. 6. 参考