ThinkPHP6.0.1_任意文件写入漏洞分析
- ThinkPHP6.0.0-ThinkPHP6.0.1
- 漏洞代码
<?php namespace app\controller; use app\BaseController; class Index extends BaseController{ public function index(){ $a = $_GET['a'];$b = $_GET['b'];session($a,$b);} }
- POC
/index.php?a=1&b=<?php phpinfo(); ?> Cookie PHPSESSID=/../../../public/aaaaaaaaaaaa.php
漏洞分析
- 先在middleware.php开启session初始化
- GET传参就不跟进了,这里直接跟进session方法,
$name
就是传入的GET参数a,$value
就是传入的GET参数b,前面那些if条件都不满足,直接进入else语句块,调用Store::set
方法。
- 跟进
Store::set
方法,其调用了Arr::set
方法
- 继续跟进,注意到
$array
是引用传参,所以在此方法上对$array
的操作就相当于对Store类的$this->data
的操作。此时$key
为GET传参a,$value
为GET传参b,因为传参的参数a没有.
,所以while条件不满足,然后就是一个键值对赋值,也就是把GET[a]=GET[b]
这个数组赋值给到Store类的$this->data
。总的来说,index.php文件里的session方法就一个作用,就对Store类的$this->data
赋值。
- 这里Session方法就结束了,好像啥也没干,但是别忘了我们最开始设置的session初始化。开启了session初始化,那么就会执行
SessionInit::handle
方法,那么跟进这个方法
$varSessionId
获取的是session配置中的var_session_id,配置在config/session.php文件,此配置默认为空
$cookieName
获取的是配置中的session name,默认为PHPSESSID,然后第一个if条件判断不满足,进入else分支,这里就是把PHPSESSID这个COOKIE值赋值给$sessionId
变量
- 然后进入
Store::setId
方法,当我们$id
,也就是$sessionId
为32位的字符串,则把$sessionId
赋值给$this->id
- 这好像就结束了?没有写入操作呀,这时利用到public/index.php文件的end方法
- 跟进end方法,其调用了
middleware::end
方法
- 继续跟进,打断点,这里发现在foreach时有一个
$instance
值为SessionInit,因为我们在middleware.php文件中定义了SessionInit中间件,那么就会调用SessionInit::end
方法
- 跟进,其继续调用了
Store::save
方法
- 跟进,
$sessionId
通过getId方法获取后就是前面设置的$this->id
,也就是PHPSESSID键的COOKIE值,这里$this->data
其实就是前面控制的键值对写入的数组数据,也就是GET[a]=GET[b]
,然后进行序列化再写入
- 调用
File::write
进行文件写入,$filename
通过getFileName方法获取
- 跟进getFileName方法,prefix默认为空,那么
$name
就是在前面拼接上sess_。然后加上路径就返回了,也就是$filename
我们部分可控
- 回到
File::write
方法进行文件写入,此时文件名部分可控,文件内容完全可控,可以进行写马
- 注意一下关键点的执行顺序,先进行
SessionInit::handle
,再执行Stroe::session
方法,最后执行SessionInit::end
方法。 - 但是正常情况下,Session的内容我们是不可控的,那么怎么利用呢?这里利用
Store::save
方法进行任意文件删除
- 文件删除POC如下
/index.php Cookie PHPSESSID=/../../../public/1111111111a.php
写在后面
- 写这个分析过程的时候是边学习边写的,中间有些理解有问题,学习完这条链才发现,然后回去改的。