Thinkphp < 6.0.2 session id未作过滤导致getshell


Thinkphp < 6.0.2 session id未作过滤导致getshell

一、漏洞简介

Thinkphp < 6.0.2 session id未作过滤导致getshell

二、漏洞影响

Thinkphp < 6.0.2

三、漏洞分析

6.0.1在设置session id时未对值进行ctype_alnum()校验,从而导致可以传入任意字符。

看一下保存session是怎么写的

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;
}

先获取sessionid,然后作为参数传入 $this->handler->write($sessionId, $data),$this->handler在构造函数中被初始化

跟进查看一下

public function __construct($name, SessionHandlerInterface $handler, array $serialize = null)
{
    $this->name    = $name;
    $this->handler = $handler;

    if (!empty($serialize)) {
        $this->serialize = $serialize;
    }

    $this->setId();
}

可以看出$handler的类型是SessionHandlerInterface,全局发现这是一个接口,实现这个接口的类有两个,一个是File,一个是Cache。这里以File类为例,我们跟进它的write()方法

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);
}

这里先通过第一个参数(也就是session id)来构造$filename,然后判断是否需要对session数据进行压缩,默认是不需要的,最后return时调用$this->writeFile()。先看看文件名是如何构造的,跟进$this->getFileName()

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;
    ...
    return $filename;
}

这里直接将第一个参数拼接到路径的最后。跟进之前的$this->writeFile()方法

protected function writeFile($path, $content): bool
{
    return (bool) file_put_contents($path, $content, LOCK_EX);
}

这里用到了file_put_contents()保存文件,但是程序未对session id进行危险字符判断。

所以我们可以利用该漏洞来写入webshell.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);
    }
	 	

这里的$sessionId的值由$request->cookie($cookieName)获得,$cookieName经过跟进后发现默认是PHPSESSID

1

所以我们可以设置Cookie中的PHPSESSID的值为xxxxx.php

四、漏洞复现

复现之前我们需要开启session

\app\middleaware.php中把\think\middleware\SessionInit::class注释取消

2

然后在\app\controller\index.php中加入入口

3

然后bp抓包,构造poc

4

发现写入成功了,访问一下写入的文件

5

五、漏洞复现

1、对session id 加一个过滤,使用ctype_alnum()

$this->id = is_string($id) && strlen($id) === 32 ctype_alnum($id&& ? $id : md5(microtime(true) . session_create_id());

2、更新Thinkphp版本


文章作者: H3h3QAQ
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 H3h3QAQ !
  目录