Thinkphp <= 5.0.10 缓存getshell复现
Thinkphp <= 5.0.10 缓存getshell复现
0x01 poc
首先看缓存函数的使用场景
然后会生成以下缓存文件
可以看到,字符串abc直接存储到以php结尾的缓存文件中。尝试使用\n换行getshell
语法有错,注释一下后面的垃圾字符,成功getshell。
0x02 跟踪源码
首先跟进18行的Cache::set()函数
跟进self::init()
self::$handler此时为null,进入true块,由于上面调用的是self::init(),没有参数,故67行条件不满足。
69行,查看配置cache.type的值,发现默认为File,
故此条件也不满足,进入72行。此处以上图的cache数组作为参数,调用了self::connect()。跟进connect方法
这里通过一系列判断,根据cache.type的值,找到cache驱动为File,对应44行的think\cache\driver\File类。然后在51行进行实例化,并return。
回溯到上个函数,也直接return
继续回溯
这里调用了return过来的实例的set方法。跟进think\cache\driver\File的set方法
可以看到,在142行调用了getCacheKey方法。
跟进getCacheKey方法后发现,这里由于options['cache_subdir']默认值是true,所以这里直接用参数md5加密后的结果的前两位作为目录名,剩余30位作为缓存文件名。然后通过拼接.php后return。
继续回来,获取上面构造的filename之后,在146行将$value进行序列化,然后在149行使用gzcompress对其进行二进制压缩。接着在151行在data前后拼接php标签,最后在152行写文件。
这里存在漏洞的点就是151行把用户可控的数据放到了php标签内。
0x03 审计思路
拿到源码后,找到Cache::set(name, value, expire),其中缓存文件名是跟name相关联的,因此可以看作是一个已知条件。漏洞的关键点就是value是否可控。
0x04 补丁
看一下修复之后的结果(v5.0.15)
这里在data之前加了一个exit()强制退出,基本杜绝了data执行php代码的可能。