更多黑客技能 公众号:暗网黑客
作者:掌控安全-veek
一. ThinkPHP 5.0.10-3.2.3 缓存函数设计缺陷可导致代码执行
0x00 背景
网站为了提高访问效率往往会将用户访问过的页面存入缓存来减少开销。
而Thinkphp 在使用缓存的时候是将数据序列化,然后存进一个 php 文件中,这使得命令执行等行为成为可能。
0x01 实验环境
系统环境
服务器主机:centOS 7
php 5.4版本,apache2,ThinkPHP 5.0.10
0x02 漏洞利用
将 application/index/controller/Index.php 文件中代码更改如下:
<?php
namespace app\index\controller;
use think\Cache;
class Index
{
public function index()
{
Cache::set("name",input("get.username"));
return 'Cache success';
}
}
访问 http://localhost/tpdemo/public/?username=xxx%0d%0aphpinfo();//
,即可将 webshell 等写入缓存文件。
0x03 漏洞分析
首先,查看缓存调用的方法
跟进set方法,可见此处动态实例化了一个类
从这个代码中我们可以看出来,这里获取外部一个配置的数组然后传
进了方法,而默认的cache.type为“File”,这个方法会根据我们的配置数据来动态的调用类执行不同的缓存操作
查看connect函数,这里通过一系列判断,根据cache.type的值,找到cache驱动为File,对应44行的think\cache\driver\File类。然后在51行进行实例化,并将其return。
跟进到file类的set函数,可以看到 data 数据没有经过任何处理,只是序列化后拼接存储在文件中,这里的 $this->options[‘data_compress’] 变量默认情况下为 false ,所以数据不会经过 gzcompress 函数处理。虽然在序列化数据前面拼接了单行注释符 // ,但是我们可以通过注入换行符绕过该限制。
最后查看一下文件名的生成方法,可以看到文件名是通过调用 getCacheKey 方法获得的。
缓存文件的子目录和文件名均和缓存类设置的键有关(如本例中缓存类设置的键为 name )。
程序先获得键名的 md5 值,然后将该 md5 值的前 2 个字符作为缓存子目录,后 30 字符作为缓存文件名。
如果应用程序还设置了前缀 $this->options[‘prefix’] ,那么缓存文件还将多一个上级目录。
总结一下,在源码中找到Cache::set(name, value, expire),其中缓存文件名是跟name相关联的,因此可以看作是一个已知条件。
漏洞的关键点就是是否开启缓存,value是否可控。
0x04 修复方案
从重现过程可以看出,在缓存文件里攻击者通过换行写入了恶意代码。可以参考引用文章中的修复方法:
1,打开文件:thinkphp\library\think\cache\driver\File.php
2,找到:public function set($name, $value, e x p i r e = n u l l ) 方 法 3 , 添 加 : expire = null) 方法 3,添加: expire=null)方法3,添加:data = str_replace(PHP_EOL, ”, $data); 即去掉换行。
0x05 参考资料
[ThinkPHP 5.0.10-3.2.3 缓存函数设计缺陷可导致 Getshell]
Thinkphp缓存函数设计缺陷getshell漏洞重现及分析
二. ThinkPHP 5.x 变量覆盖导致的文件包含&任意代码执行
0x00 背景
影响版本:5.0.0<=ThinkPHP5<=5.0.18 、5.1.0<=ThinkPHP<=5.1.10。
ThinkPHP在加载模版解析变量时存在变量覆盖的问题,且没有对 $cacheFile 进行相应的消毒处理,导致模板文件的路径可以被覆盖,从而导致任意文件包含漏洞的发生。
0x01 漏洞复现
环境:centOS 7, php 5.4, ThinkPHP 5.0.10
首先将 application/index/controller/Index.php
文件代码设置如下:
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
public function index()
{
$this->assign(request()->get());
return $this->fetch();
}
}
创建 application/index/view/index/index.htm