Thinkphp 5.0.x 远程命令执行漏洞
漏洞概述
本次漏洞存在于 ThinkPHP 的缓存类中。该类会将缓存数据通过序列化的方式,直接存储在 .php 文件中,攻击者通过精心构造的 payload ,即可将 webshell 写入缓存文件。缓存文件的名字和目录均可预测出来,一旦缓存目录可访问或结合任意文件包含漏洞,即可触发 远程代码执行漏洞 。
影响版本
- 5.0.0 <= ThinkPHP <= 5.0.10
漏洞环境
拉取项目
1 | composer create-project --prefer-dist topthink/think=5.0.10 thinkphp_5.0.10 |
修改 composer.json 文件的 require 字段设置成如下,然后再执行 composer update
1 | "require": { |
使用 phpstudy 搭建选择网站根目录为 ./thinkphp_5.0.10

修改 application/index/controller/Index.php 文件内容
1 |
|
漏洞复现
1 | http://thinkphp/public/?username=mochazz123%0D%0A@eval($_GET[_]);// |

漏洞分析
打断点进入 Cache 类中 set() 方法

位于 thinkphp/library/think/Cache.php 中 126 行

调用 self::init() 初始化缓存,继续跟进

self::$handler 为 null ,进入 if 判断条件中,上面调用 init() 没有参数,所以 $options 为空,进入 69 行判断

而 cache.type 默认值为 File

所以根据条件进入 72 行判断,继续紧跟 self::connetct()

$options['type'] 值为 File ,找到 cache 缓存的驱动为 File,对应类为 \think\cache\driver\file,在第 51 行处实例化并返回,进入 \think\cache\driver\file 中 set() 方法
$value 参数可空,没有任何过滤,经过 serialize() 序列化赋值给 $data ,第 147 行处 $this->options['data_compress'] 默认为 false ,数据不会经过 gzcompress() 函数处理,直接拼接 $data 数据,序列化前面的单行注释符 // 通过传入的换行符 %0D%0A 进行绕过,最后使用 file_put_contents() 写入。文件名 $filename 来自 getCacheKey() ,跟进该函数查看

第 70 行处 $this->options['cache_subdir'] 默认值为 true ,进入判断中,取 $name 的 md5 加密后的值前两位拼接 / 后面剩余的 28 位字符串为完整的路径信息。
利用补充
- 该漏洞利用需要站点搭建在
thinkphp根目录,可以访问到runtime目录中,缓存文件可访问到。而官方推荐public为 web 目录。 - 如果程序设置
$this->options['prefix']的情况下,没有源码则无法判断webshell路径。