Thinkphp3文件包含(CNVD-2024-39045)

admin 2024年11月13日10:13:26评论25 views字数 6818阅读22分43秒阅读模式

声明:请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关。

一.基础信息  

一个月前发了一个tp5的鸡肋漏洞,限制必须只能windows系统,这里的tp3就不限制系统,但是还是存在一定的鸡肋,具体看下面文章吧,在7月份写文章的时候那天晚上喝酒了,可能会有错别字或者不通顺的地方,哈哈也是文化水平有限,过一两个月我会发禅道的前台rce,本想着不公开的但是在朋友那块得知貌似hw的时候见过眼熟这里我也不知真假,最近也一直在学车考驾照真是难死我了,希望在年前能拿到驾照。

time 7.15

测试版本Version: 3.2.5

php: 7.3.4

影响范围:<= 3.2.5

漏洞利用poc: index.php?m=Home&c=index&a=../../../../../文件名

调用堆栈:

File.class.php:88, ThinkStorageDriverFile->load()            Storage.class.php:41, call_user_func_array:{D:phpStudyWWWthinkphp-3.2.5ThinkPHPLibraryThinkStorage.class.php:41}()            Storage.class.php:41, ThinkStorage::__callStatic()            Template.class.php:91, ThinkStorage::load()            Template.class.php:91, ThinkTemplate->fetch()            ParseTemplateBehavior.class.php:38, BehaviorParseTemplateBehavior->run()            Hook.class.php:131, ThinkHook::exec()            Hook.class.php:99, ThinkHook::listen()            View.class.php:155, ThinkView->fetch()            View.class.php:77, ThinkView->display()            Controller.class.php:62, HomeControllerIndexController->display()            Controller.class.php:185, HomeControllerIndexController->__call()            App.class.php:118, ReflectionMethod->invokeArgs()            App.class.php:118, ThinkApp::exec()            App.class.php:214, ThinkApp::run()            Think.class.php:139, ThinkThink::start()            ThinkPHP.php:100, require()            index.php:26, {main}()

Thinkphp3文件包含(CNVD-2024-39045)    

二.审计过程  

1.1: 路由分析  

在Thinkphp5中使用使用最多的就两种方式,但在Thinkphp3中而是三种

http://127.0.0.1/index.php?m=模块&c=控制器&a=方法

http://127.0.0.1/index.php?s=模块/控制器/方法

上面是可以在文档中查到的,下面分析在代码中的实现。

Thinkphp3文件包含(CNVD-2024-39045)

常量的话没遇危险点不用看,ThinkPHP.php又定义了许多常量,在最后面执行了Think::start(),

给进去看看弄了啥,这里面也就是加载了一些配置文件都是写死的没法利用,在这个方法最后面执行了App下面的run方法,但是这块存在三个App类具体是哪一个,可以看到下面61行的时候存在一个inculde

其实下面有好几个但是大致看一下注释就知道是这了,print_r($mode['core']); 发现执行的是

ThinkPHP/Library/Think/App.class.php。

Thinkphp3文件包含(CNVD-2024-39045)

Thinkphp3文件包含(CNVD-2024-39045)

跟进去看看干了什么,虽然上面存在很多inculde但是没有一个我们能控制的,所以接着看,下面就是执行到了run()
public static function run()            {                // 加载动态应用公共文件和配置                load_ext_file(COMMON_PATH);                // 应用初始化标签                Hook::listen('app_init');                App::init();                // 应用开始标签                Hook::listen('app_begin');                // Session初始化                if (!IS_CLI) {                    session(C('SESSION_OPTIONS'));                }                // 记录应用初始化时间                G('initTime');                App::exec();                // 应用结束标签                Hook::listen('app_end');                return;            }            其中load_ext_file(COMMON_PATH); COMMON_PATH是/Application/Common/ ,load_ext_file            方法我看了一下其实啥也没干看下图C('LOAD_EXT_FILE')是空的导致两个条件都进不去。Hook::listen('app_init');也差不多,对于审计来说确实意义不大。重点:App::init();看下面代码

Thinkphp3文件包含(CNVD-2024-39045)    

刚开始写了一些常量$_SERVER['REQUEST_TIME'] 时间戳,$_SERVER['REQUEST_METHOD']请求类型,目前看的话没啥东西,直接看Dispatcher::dispatch();但是Dispatcher类又存在三个类和同样的方法具体是哪一个,可以看找App类的图这块我就不在截图了哈。
public static function init()            {                // 日志目录转换为绝对路径 默认情况下存储到公共模块下面                C('LOG_PATH', realpath(LOG_PATH) . '/Common/');                // 定义当前请求的系统常量                define('NOW_TIME', $_SERVER['REQUEST_TIME']);                define('REQUEST_METHOD', $_SERVER['REQUEST_METHOD']);                define('IS_GET', REQUEST_METHOD == 'GET' ? true : false);                define('IS_POST', REQUEST_METHOD == 'POST' ? true : false);                define('IS_PUT', REQUEST_METHOD == 'PUT' ? true : false);                define('IS_DELETE', REQUEST_METHOD == 'DELETE' ? true : false);                // URL调度                Dispatcher::dispatch();                if (C('REQUEST_VARS_FILTER')) {                    // 全局安全过滤                    array_walk_recursive($_GET, 'think_filter');                    array_walk_recursive($_POST, 'think_filter');                    array_walk_recursive($_REQUEST, 'think_filter');                }                // URL调度结束标签                Hook::listen('url_dispatch');                define('IS_AJAX', ((isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') || !empty($_POST[C('VAR_AJAX_SUBMIT')]) || !empty($_GET[C('VAR_AJAX_SUBMIT')])) ? true : false);                // TMPL_EXCEPTION_FILE 改为绝对地址                C('TMPL_EXCEPTION_FILE', realpath(C('TMPL_EXCEPTION_FILE')));                return;            }            
看到这呢根据注释已经可以看出找到第二种方式了,$varPath输出一下其实就是s,然后我们一直看到    145行的时候执行了define('MODULE_NAME', self::getModule($paths));其中$paths给到MODULE_NAME这个常量,跟进去看看,为啥看这个方法因为我在这个下面看到一个if用到了这个常量可以看下图。

Thinkphp3文件包含(CNVD-2024-39045)

Thinkphp3文件包含(CNVD-2024-39045)

第一个if输出一下就知道了,第2个$paths本身就是空,第三个一样的,所以他只能走到else,$var输出是m,$module获取了GET的参数在进行了销毁,第四个直接过,第五个C('URL_MODULE_MAP')是空的,最后直接return返回$module了,然后返回到Dispatcher类,is_dir(APP_PATH . MODULE_NAME)    MODULE_NAME目前是我们能控制的,所以必须走下去不然程序直接就结束了,APP_PATH是Application目录这个目录下面有模块Home,其实大致也就清楚了这是第一种路由,走进去之后又是一堆include但是没有能控制的,唯独load_ext_file(MODULE_PATH);可以控制但是上面说过load_ext_file这个方法啥也没干,走到218行和219行进去之后也验证了我们的猜想,代码我就不解释了和模块代码大差不差,下面是图。

Thinkphp3文件包含(CNVD-2024-39045)

Thinkphp3文件包含(CNVD-2024-39045)

Thinkphp3文件包含(CNVD-2024-39045)

Thinkphp3文件包含(CNVD-2024-39045)

之后就是执行了一些对本次漏洞没太大意义的东西就不说了,回到init后面也是没啥意义的东西。

1.2:漏洞执行过程 

面分析了路由下面我们看看这次的文件包含如何产生的,回到run()方法,215行执行了App::exec();跟进去看看,CONTROLLER_NAME我么再熟悉不过了不就是刚才c的常量吗 /^[A-Za-z](/|w)*$/  必须是这个正则的内容 不是的话$module是false,就会执行到95行 exit就结束了,所以我们正常输入其中几个if大家输出一下就知道了我就不废话了,会走到else里面 这个方法不就是控制器的意思吗跟进去看看controller。

Thinkphp3文件包含(CNVD-2024-39045)

function controller($name, $path = ''){    $layer = C('DEFAULT_C_LAYER');    if (!C('APP_USE_NAMESPACE')) {        $class = parse_name($name, 1) . $layer;        import(MODULE_NAME . '/' . $layer . '/' . $class);    } else {        $class = ($path ? basename(ADDON_PATH) . '\' . $path : MODULE_NAME) . '\' . $layer;        $array = explode('/', $name);        foreach ($array as $name) {            $class .= '\' . parse_name($name, 1);        }        $class .= $layer;    }    if (class_exists($class)) {        return new $class();    } else {        return false;    }}
$class = ($path ? basename(ADDON_PATH) . '\'. $path : MODULE_NAME) . '\' . $layer;    $array = explode('/', $name);    foreach ($array as $name) {        $class .= '\' . parse_name($name, 1);    }    $class .= $layer;}if (class_exists($class)) {    return new $class();上面的是重点,其他的不用看,MODULE_NAME常量是m $layer是controller $name就是刚开始传过来的常量 CONTROLLER_NAME也就是c(class_exists($class)查看我们输出的类存不存在,存在直接new不存在就false下面就是109行ACTION_NAME是a,给到$action,113行进去看看,里面其实做了一个反射,主要看117行通过反射动态执行了一个__call魔术方法,但是要执行到116行必须要存在一个错误,所以还是要进去看看113行。

Thinkphp3文件包含(CNVD-2024-39045)

$action是我们能控制的a,只要不是这个正则里面的就能执行到116行了$module也就是我们的类。

Thinkphp3文件包含(CNVD-2024-39045)

那么Application/Home/Controller/IndexController.class.php并没有__call这个方法呀?难道必须我们自己写吗?那这叫什么狗屁漏洞,别急IndexController类确实没有但是默认会继承一个父类Controller类。

Thinkphp3文件包含(CNVD-2024-39045)

可以看到确实存在这个魔术方法,$method->invokeArgs($module, array($action, '')); array($action, '')其中$action是我们可以控制的也就是a,function __call($method, $args) $method也就是$action。

等等我在__call看到了什么$this->display();搜嘎我相信到这里就清楚了吧,第一个if输出一下就知道了,第2个method_exists判断当前类里面存在不存在_empty,也可以过了因为默认没有,parseTemplate跟进去看看呗。

Thinkphp3文件包含(CNVD-2024-39045)

if ('' == $template) {      // 如果模板文件名为空 按照默认规则定位      $template = CONTROLLER_NAME . $depr . ACTION_NAME;  } elseif (false === strpos($template, $depr)) {      $template = CONTROLLER_NAME . $depr . $template;  }  $file = THEME_PATH . $template . C('TMPL_TEMPLATE_SUFFIX');  if (C('TMPL_LOAD_DEFAULTTHEME') && THEME_NAME != C('DEFAULT_THEME') && !is_file($file)) {      // 找不到当前主题模板的时候定位默认主题中的模板      $file = dirname(THEME_PATH) . '/' . C('DEFAULT_THEME') . '/' . $template . C('TMPL_TEMPLATE_SUFFIX');  }  return $file;这个是漏洞产生的原因,也是执行到$this->display();造成文件包含的原因,因为$this->display();里面也执行了parseTemplate方法。$template本来就是空的,ACTION_NAME常量是我们一路看过来的东西就是a,C('TMPL_TEMPLATE_SUFFIX')输出了一下是html的,然后返回了$file。

然后还有一个file_exists_case方法我就不解释了下面是图就是看文件存在不存在。

Thinkphp3文件包含(CNVD-2024-39045)

最后执行了$this->display();跟进去。

Thinkphp3文件包含(CNVD-2024-39045)

继续呗。

Thinkphp3文件包含(CNVD-2024-39045)

看看fetch,121行和我上面说的一样就不进去看了,$templateFile是我们可以控制的,到了154行给了一个数组里面$params,155行进去看看对数组做了什么。

Thinkphp3文件包含(CNVD-2024-39045)

Thinkphp3文件包含(CNVD-2024-39045)

Thinkphp3文件包含(CNVD-2024-39045)

接着看看self::exec

Thinkphp3文件包含(CNVD-2024-39045)

不知道的直接输出一下。

Thinkphp3文件包含(CNVD-2024-39045)当前类的run方法。

Thinkphp3文件包含(CNVD-2024-39045)

Template类的fetch

Thinkphp3文件包含(CNVD-2024-39045)

接着进入loadTemplate这块我就不具体说了可以看Thinkphp3变量覆盖导致rce的文章,简单来说就是把文件进行读取出来130把内容进行过滤(这块的过滤可以忽略),然后创建缓存文件,131行把内容写入到缓存文件里面,最后返回这个缓存文件的路径之后执行Storage::load。

Thinkphp3文件包含(CNVD-2024-39045)

Storage::load跟进去看看

Thinkphp3文件包含(CNVD-2024-39045)

Thinkphp3文件包含(CNVD-2024-39045)

到这也就是成功进行文件包含了但是存在条件就是,上面我们分析到C('TMPL_TEMPLATE_SUFFIX')

是html所以只能包含html的文件。

Thinkphp3文件包含(CNVD-2024-39045)

三.漏洞利用

首先在根目录下面创建一个文件名字随便但是必须是html扩展名,内容为php的代码,就phpinfo吧poc:

http://127.0.0.1/index.php?m=Home&c=index&a=../../../../../2

Thinkphp3文件包含(CNVD-2024-39045)

Thinkphp3已经不再更新维护了,但是有很多优秀项目依然在使用tp3,这里就简单举个例子,在我写这篇文章的时候showdoc-3.2.6目前最新版本一下都存在这个漏洞。

四:GETshell

还在尝试中......    

微信公众号用不习惯格式可能会有部分错误,查看语雀原文公众号回复:Think3

原文始发于微信公众号(摸鱼Sec):Thinkphp3文件包含(CNVD-2024-39045)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月13日10:13:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Thinkphp3文件包含(CNVD-2024-39045)https://cn-sec.com/archives/3391020.html

发表评论

匿名网友 填写信息