漏洞简介
think\Session中的setId方法未对PHPSESSID进行校验,因此造成了任意文件写漏洞,漏洞存在版本:ThinkPHPv6.0.0-v6.0.1
配置
1 | composer create-project topthink/think tp6 |
在app/controller/Index.php下修改代码
1 |
|
在app/middleware.php开启session功能(默认开启)
官网关于session的配置:https://www.kancloud.cn/manual/thinkphp6_0/1037635
1 |
|
正常访问页面如下

漏洞演示
cookie设置为
1 | PHPSESSID=/../../../public/shell123456.php |
需要32位的Cookie,然后通过c传参设置cookie中的demo
1 | /?c= phpinfo(); |
访问shell123456.php

漏洞分析
在框架运行前,先进入src/think/middleware/SessionInit.php的handle方法初始化session
1 | public function handle($request, Closure $next) |

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

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

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

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

跟进session方法初始化session

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

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

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

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

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

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

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

通过$this->getFileName获取$sessionId中的文件名
1 | protected function getFileName(string $name, bool $auto = false): string |
直接将sess和$sessionId拼接到一起形成sess_/../../../public/shell123456.php为$filename,然后用dirname截取$dir创建目录sess_

最后在writeFile函数中写入shell文件,分析到这里就结束了
漏洞修复
在src/think/session/Store.php处的setId使用了ctype_alnum方法确保传入的session文件名是字母和数字

