ThinkPHP 配置不当可导致远程代码执行
漏洞分析报告
1. 漏洞描述
ThinkPHP 是一款国内流行的开源 PHP 框架, 近日被爆出存在可能的远程代码执行漏洞, 攻击者可向缓存文件内写入 PHP 代码, 导致远程代码执行. 虽然该漏洞利用需要有几个前提条件, 但鉴于国内使用 ThinkPHP 框架的站点数量之多, 该漏洞还是存在一定的影响范围.
2. 漏洞危害
攻击者可通过该漏洞获取网站的控制权限, 可修改页面, 导致数据泄漏等问题.
3. 影响版本
ThinkPHP 3,ThinkPHP 5
4. 漏洞利用前置条件
缓存名已知 (使用了开源程序 / 源码泄漏等情况), 或者缓存名可猜测 (使用了常见的名词, 如 user, news, goods 等).
/runtime / 目录下的文件可通过 web 访问.
缓存内容可控或部分可控, 可带入 PHP 代码.
5. 风险等级
高危
6. 漏洞分析
以 ThinkPHP 3.2.3 为例:
这是一个简单的新闻管理页面, 首页调用 index 方法, 列出系统内已有的新闻标题. addnews 方法用于插入一条新的新闻数据. detail 方法通过获取新闻的 tid 号查询出新闻的详细内容.
由于新闻的内容较多, 查询较慢, 通常采用缓存的方式提高访问速度. 在这时, 如果采用数据缓存的方式, 就会产生安全隐患.
这里的逻辑是如果能查到缓存, 则直接读取缓存数据, 否则从数据库里查出数据, 并调用 S 函数进行缓存. S 函数的实现:
首先实例化一个 Think\Cache 类, 然后调用该类的 set 方法. 首先看一下实例化操作:
实例化了 Cache 类, 并调用 Connect 方法:
可以在 ThinkPHP/Conf/convention.php 中找到 DATA_CACHE_TYPE 的默认值为 File:
$class='Think\\Cache\\Driver\\File', 所以 S 函数的操作为实例化 File 类并调用其 set 方法:
在这里可以看到 $value 的值只是被序列化之后就传入 // 注释后, 而 // 注释符只是一个单行注释, 只需传入换行符就能绕过防御. 在 137 行,$data 数据被写入文件, 文件名在 125 行被赋值, 跟进一下 filename 这个函数:
$name = md5(C('DATA_CACHE_KEY').$name) 而 DATA_CACHE_KEY 在配置文件中默认为空:
于是当访问 http://127.0.0.1/thinkphp323/home/news/detail?tid=1 时管理页面会去寻找缓存目录下 md5(new_1)= b00e5c9873c2a37766a94d11c19131e8.php 这个文件.
下图是访问 http://127.0.0.1/thinkphp323/home/news/detail?tid=1 的结果:
缓存文件的内容:
按照上面的分析, 通过 addnews 方法插入一段恶意的代码:
然后用 detail 方法查看 tid 为 2 的记录触发数据缓存:
利用上述计算方法, 此时产生的缓存文件名为 md5(new_2)= 04f886bf57f9070935c4a0f972032380.php. 内容如下:
直接在浏览器访问:
可以发现输入的恶意代码已经被执行了.
修复建议
在查阅相关资料过程中, 发现有文章指出用如下代码进行防御:$data=str_replace(PHP_EOL, '', $data); 经过分析和测试, 该防御方式存在绕过方式, 而且会影响程序的缓存功能.
新版的 ThinkPHP 中已经有对缓存的内容进行一定的防护, 但是依然可以绕过防护, 写入 PHP 代码. 因此主要还是要依靠用户进行安全的配置, 为了避免该漏洞产生, 安恒应急响应中心建议:
1. 用户在部署程序时勿将 ThinkPHP 中 / public / 目录之外的其它目录 (尤其是 / runtime/) 放置到 Web 目录下.
2. 开发者在程序安装完成后使程序随机生成 DATA_CACHE_KEY(在 convention.php 文件中), 以免缓存文件名被猜测出来.
对于已经在使用 ThinkPHP 框架的用户, 可以按下面方法加固站点:
* 进行修改之前请提前备份好数据!
1. 检查 Web 目录下是否存在 / runtime/(/Runtime/) 目录, 或 /application/(/Application/) 目录, 如果存在这两个目录, 则极有可能未正确部署程序.
修改 HTTP 服务配置, 将 Wen 路径修改到 ThinkPHP 的./public 目录.
2. 添加缓存文件名前缀
在 ThinkPHP3 中: 将 convention.php 文件中的 DATA_CACHE_KEY 设置为随机字符串.
参考: https://www.kancloud.cn/manual/thinkphp/1835
在 ThinkPHP5 中: 在缓存初始化操作之前, 设置文件前缀
- ``
- $options = [
- // 缓存类型为 File
- 'type' => 'File',
- // 缓存有效期为永久有效
- 'expire'=> 0,
- // 缓存前缀
- 'prefix'=> 'think',
- // 指定缓存目录
- 'path' => APP_PATH.'runtime/cache/',
- ];
- Cache::connect($options);
- ``
- - END -
来源: https://cloud.tencent.com/developer/article/1089169?formSource=waitui