Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[f5_bigip_cookie_disclosure] Store cookies in the database #12217

Closed
wants to merge 14 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
## Vulnerable Application

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.

## Verification Steps

1. Start `msfconsole`
1. Do: `use auxiliary/gather/f5_bigip_cookie_disclosure`
1. Do: `set RHOSTS www.example.com`
1. Do: `run`

## Options

### REQUESTS

The number of requests to send. Default value is `10`.

## Scenarios

### F5 BIP-IP load balancing cookie not found

```
msf5 > use auxiliary/gather/f5_bigip_cookie_disclosure
msf5 auxiliary(gather/f5_bigip_cookie_disclosure) > set RHOSTS www.example.com
RHOSTS => www.example.com
msf5 auxiliary(gather/f5_bigip_cookie_disclosure) > run
[*] Running module against 93.184.216.34

[*] Starting request /
[-] F5 BIG-IP load balancing cookie not found
[*] Auxiliary module execution completed
msf5 auxiliary(gather/f5_bigip_cookie_disclosure) >
```

### F5 BIP-IP load balancing cookie found

```
msf5 > use auxiliary/gather/f5_bigip_cookie_disclosure
msf5 auxiliary(gather/f5_bigip_cookie_disclosure) > set RHOSTS vulnerable-target.com
RHOSTS => vulnerable-target.com
msf5 auxiliary(gather/f5_bigip_cookie_disclosure) > run
[*] Running module against 1.1.1.1

[*] Starting request /
[+] F5 BIG-IP load balancing cookie "BIGipServer~DMZ~EXAMPLE~vulnarable-target-443_pool = 1214841098.47873.0000" found
[+] Load balancing pool name "~DMZ~EXAMPLE~vulnarable-target-443_pool" found
[+] Backend 10.1.105.72:443 found
[*] Auxiliary module execution completed
msf5 auxiliary(gather/f5_bigip_cookie_disclosure) > notes

Notes
=====

Time Host Service Port Protocol Type Data
---- ---- ------- ---- -------- ---- ----
2019-08-20 21:21:02 UTC 1.1.1.1 f5_load_balancer_cookie_name "BIGipServer~DMZ~EXAMPLE~vulnarable-target-443_pool"
2019-08-20 21:21:02 UTC 1.1.1.1 f5_load_balancer_pool_name "~DMZ~EXAMPLE~vulnarable-target-443_pool"
2019-08-20 21:21:02 UTC 1.1.1.1 f5_load_balancer_backends [{:host=>"10.1.105.72", :port=>443}]
msf5 auxiliary(gather/f5_bigip_cookie_disclosure) >
```
116 changes: 70 additions & 46 deletions modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,69 +8,77 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(update_info(info,
'Name' => 'F5 BigIP Backend Cookie Disclosure',
'Description' => %q{
This module identifies F5 BigIP load balancers and leaks backend
information (pool name, backend's IP address and port, routed domain)
through cookies inserted by the BigIP system.
},
'Author' =>
[
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>'
'Denis Kolegov <dnkolegov[at]gmail.com>',
'Paul-Emmanuel Raoul <skyper@skyplabs.net>'
],
'References' =>
[
['URL', 'http://support.f5.com/kb/en-us/solutions/public/6000/900/sol6917.html'],
['URL', 'http://support.f5.com/kb/en-us/solutions/public/7000/700/sol7784.html?sr=14607726']
'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
'License' => MSF_LICENSE,
'DefaultOptions' => {
'SSL' => true
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => []
}
))
)
)

register_options(
[
OptInt.new('RPORT', [true, 'The BigIP service port to listen on', 443]),
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
conversion = [value].pack('V').unpack('N').first
elsif size == 2
conversion = [value].pack("v").unpack("n").first
conversion = [value].pack('v').unpack('n').first
end
conversion
end

def cookie_decode(cookie_value)
backend = {}
case
when cookie_value =~ /(\d{8,10})\.(\d{1,5})\./
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)
when cookie_value.downcase =~ /rd\d+o0{20}f{4}([a-f0-9]{8})o(\d{1,5})/
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)
when cookie_value.downcase =~ /vi([a-f0-9]{32})\.(\d{1,5})/
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)
when cookie_value.downcase =~ /rd\d+o([a-f0-9]{32})o(\d{1,5})/
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)
Expand All @@ -84,9 +92,10 @@ def cookie_decode(cookie_value)
backend
end

def get_cookie # request a page and extract a F5 looking cookie.
def fetch_cookie
# Request a page and extract a F5 looking cookie
cookie = {}
res = send_request_raw({ 'method' => 'GET', 'uri' => @uri })
res = send_request_raw('method' => 'GET', 'uri' => @uri)

unless res.nil?
# Get the SLB session IDs for all cases:
Expand All @@ -96,41 +105,47 @@ def get_cookie # request a page and extract a F5 looking cookie.
# 4. IPv6 pool members in non-default route domains - "BIGipServerWEB=rd3o20010112000000000000000000000030o80"

regexp = /
([~_\.\-\w\d]+)=(((?:\d+\.){2}\d+)|
([~.\-\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]
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 = get_cookie # Get the cookie
cookie = fetch_cookie # Get the cookie
# If the cookie is not found, stop process
if cookie.empty? || cookie[:id].nil?
print_error("F5 BigIP load balancing cookie not found")
return
print_error('F5 BIG-IP load balancing cookie not found')
return nil
end

# Print the cookie name on the first request
if i == 1
print_good("F5 BigIP load balancing cookie \"#{cookie[:id]} = #{cookie[:value]}\" found")
cookie_name = cookie[:id]
print_good("F5 BIG-IP load balancing cookie \"#{cookie_name} = #{cookie[:value]}\" found")
if cookie[:id].start_with?('BIGipServer')
print_good("Load balancing pool name \"#{cookie[:id].split('BIGipServer')[1]}\" found")
pool_name = cookie[:id].split('BIGipServer')[1]
print_good("Load balancing pool name \"#{pool_name}\" found")
end
if cookie[:value].start_with?('rd')
print_good("Route domain \"#{cookie[:value].split('rd')[1].split('o')[0]}\" found")
route_domain = cookie[:value].split('rd')[1].split('o')[0]
print_good("Route domain \"#{route_domain}\" found")
end
end

Expand All @@ -141,16 +156,25 @@ def run
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
print_error("Network connection error")
rescue ::Rex::ConnectionError
print_error("Network connection error")
rescue ::OpenSSL::SSL::SSLError
print_error("SSL/TLS connection error")
rescue ::Rex::ConnectionRefused, ::Rex::ConnectionError
print_error('Network connection error')
rescue ::OpenSSL::SSL::SSLError
print_error('SSL/TLS connection error')
end
end