thinkphp 缓存函数设计缺陷 getshell
0x00 前言&&影响版本
不能说是thinkphp漏洞,因为官方压根就没承认,按照官方手册配置使用这个缓存功能是没有任何问题的,但单从其缓存函数上看确实存在因为未加过滤引起的可能getshell。1
2
3影响版本
thinkphp 3.2.3-5.0.10
0x01 利用条件
以下分析仅在理想环境下用于本地漏洞复现,生产环境下咯多漏洞利用难度系数较高,需要满足以下几个条件
- 开启缓存功能
- 缓存文件所在目录可以从浏览器直接访问,因为缓存文件可能不部署在web目录
- 需要能够猜解出缓存的文件名(文件名生成有一定规律)
- 同时可以控制缓存文件里的内容(比如通过写入到数据库进而生成缓存文件)
0x02 分析过程
以5.0.10版本为例。
生成新的Home模块1
php think build --module home
1 | //demo |
Demo建好了,add()方法用于向数据库中插入数据,cache()方法用于缓存数据。
下面开始分析代码。
当访问下面的url调用cache方法时
1 | http://127.0.0.1/thinkphp_5.0.10_full/public/index.php/Home/index/cache |
函数调用栈
这里重点说一下 thinkphp\cache]driver\File下的set()方法
首先跟入getCacheKey方法可以看到,获取到的是缓存文件目录,其将缓存名称经过md5(),前两位作为目录名,后面的作为缓存文件名。
其通过$this->option[‘path’]获得目录前缀,其默认是在 Runtime/cache/,可以通过修改CACHE_PATH使缓存目录放在非web目录,从而避免漏洞被利用。
获取到缓存文件名之后继续向下运行,可以看到将要缓存的数据进行序列化之后直接拼接写入到了缓存目录,也就是这里的数据没有做任何过滤,所以出现了漏洞。
这里的data虽然拼接到了注释语句中,但是因为没有任何过滤可以通过注入换行符跳出单行注释,而且序列化函数不会对换行符做任何处理。
那么我们便可以通过demo中的add()方法先向数据库存入payload
数据中
然后访问cache方法,既可以看到paylaod进入到了缓存文件
1 | //payload |
0x03 修复方法
完善框架中set()方法,在/thinkphp/library/think/cache/driver/File.php 中的set()函数中对于$value参数进行过滤,去除换行符号。添加代码$data = str_replace(PHP_EOL, ‘’, $data);但存在因为去除换行符导致的文本布局改变。
从上面的审计中可以知道文件名是由缓存索引的md5值决定的,所以可以设置复杂的缓存索引。
修改CHCHE_PATH,使缓存目录放在用户不可访问的地方。
从php的配置入手,关闭eval等危险函数。