Halloween has come and gone, and yet NTLM reflection is back from the dead to haunt MSRC once again. This post describes a deceptively simple bug that has existed in Windows for 15 years.
NTLM reflection is still possible through a highly reliable timing attack. The attack works by abusing the logic responsible for its mitigation, a widely speculated challenge cache. Attackers can purge this cache by deliberately failing an authentication attempt and doing so removes all challenge entries older than 5 minutes.
A Brief History of NTLM Reflection Bugs
- SirDystic Publishes SMBRelay
- Microsoft Release Windows XP SP2
- Microsoft Mitigate SMB/SMB Reflection in MS08-68
- Microsoft Mitigate HTTP/SMB Reflection in MS09-13
- Forshaw Discovers Local WebDAV/SMB Reflection in CVE-2017-3225
- Microsoft Issue a WONTFIX for CVE-2017-3225
- Forshaw Discovers DCOM DCE/RPC Local NTLM Reflection Elevation of Privilege
- Foxglove Weaponise CVE-2017-3225 in Hot Potato
- Microsoft Mitigate Hot Potato in MS16-075
- Foxglove and Forshaw abuse SeImpersonatePrivilege and local NTLM reflection in Rotten Potato
- Microsoft Mitigate Rotten Potato in later Windows versions (1809 onwards)
An SSPI Primer
A basic understanding of the relevant APIs goes a long way when trying to understand NTLM exchanges (or most authentication exchanges in Windows for that matter) between a client and a server.
It’s best to consider a typical NTLM authentication exchange as a conversation between the following entities:
- Client process (e.g, browser.exe)
- Client’s LSASS process (i.e, lsass.exe running on the same machine as browser.exe)
- Server process (e.g, a web server, server.exe)
- Server’s LSASS process (i.e, lsass.exe running on the same machine as the web server)
Note: when the server process is on the same machine, lsass.exe refers to the same process and things behave differently as opposed to the alternative ‘non-local’ situation.
According to the documentation, the client process must call InitializeSecurityContext in a loop.
The output of InitializeSecurityContext (herein referred to as ‘ISC’), is an opaque binary blob, which the client must pass to the server (hint: for NTLM, this blob will be either a NEGOTIATE or AUTHENTICATE message).
The client must also pass into ISC opaque blobs which it receives from the server.
The client continues to call ISC in this manner until the return status becomes SEC_E_OK, indicating that the remote system has successfully authenticated the client.
The server process behaves almost identically, except that AcceptSecurityContext (herein referred to as ‘ASC’) is called instead of ISC.
The server will pass in any opaque blobs received from the client until (again, if successful), the return status becomes SEC_E_OK.
The situation can be summarised with the observation that both of the client and server processes become nothing more than ‘dumb’ proxies between calls to ISC and ASC respectively.
Ultimately, both ISC and ASC call into LSASS via LPC, and, depending on the flavour of ISC and ASC used by the client and server, will reach either NTLM, Kerberos, Digest, CredSSP, Negotiate or Schannel implementations.
NTLM Reflection Recap
In the years following SirDystic’s release of SMBRelay and up until 2008 it was possible to relay NTLM authentication back to the victim. Here is a reminder of the general process of WebDAV/SMB reflection:
The ability to do this ended in 2008 and 2009, depending on the source and destination clients respectively. For instance, NTLM reflection from an SMB client and towards an SMB server became inviable in 2008, with MS08-68. Attempting to reflect a WebDAV client back towards an SMB server ceased to work in 2009.
Let’s try to understand why that was.
The Reflection Patches (MS08-68 and MS09-13)
In 2008 Microsoft issued MS08-68, mitigating SMB/SMB reflection. I haven’t seen a description elsewhere of what the patch does exactly (admittedly, I may not have looked hard enough), but its modification is relatively simple.
MS08-68 does nothing more than changing the way in which ISC is called within the SMB client. Specifically, the pszTargetName argument, which was previously set the NULL, is now set to the SPN of the target. For example, attempting to browse to
\\bob\someshare\ will, following MS08-68, result in a call to ISC with
In 2009, Microsoft followed up the SMB/SMB mitigation with an HTTP/SMB mitigation analog, MS09-13. Can you guess what this patch does? The pszTargetName argument, previously set to NULL, is set to the target SPN following the patch. For example, browsing to
http://bob/webdav/a.png results in a call to ISC with
Looking carefully at the documentation for ISC, we can see that the pszTargetName argument does indeed have something to do with ‘replay’ mitigations.
The Challenge Cache
The wider infosec community has speculated that the mitigation against reflection involved some sort of challenge cache.
In theory, a server’s LSASS process could keep track of challenges it has issued to clients attempting to authenticate to it via server process calls to ASC. Should the same LSASS process receive a challenge via a client’s call to ISC (to generate a corresponding response), that has been previously cached in a prior call to ASC, then LSASS would be justified in suspecting some sort of NTLM reflection has taken place.
There is a problem though; this logic breaks down in the case where local authentication is taking place, i.e, where both the client and server processes are running on the same host, and hence share an instance of LSASS. For example, LSASS would be incorrect in assuming NTLM reflection has taken place, simply because a user has attempted to authenticate to
http://localhost/webdav/a.png via WebDAV. In this instance, the server process (say, webdavsvc.exe) will call ASC and pass in a NEGOTIATE message, obtaining a CHALLENGE message which it is expected to return to the client. The client will subsequently call ISC, providing the CHALLENGE message for the purpose of obtaining a response, at which point LSASS can inspect its active challenge cache. An entry in the cache indicates nothing more than the fact that the client and server processes are running on the same host, with no malicious third-party involvement.
So how does it actually work? Well, it turns out the community was largely correct, except the details were lacking (at least publicly).
The NTLM implementation tracks active challenges but also associates them with their SPN (specified via
pszTargetName during client calls to ISC).
LSASS is then able to isolate cases of NTLM reflection during server calls to ASC, when the response is provided for validation. The challenge is resolved from the challenge cache, and the associated SPN is inspected. If SPN hostname does not match one from a list of legitimate aliases for the host, then this is a strong indication that the client’s original intention was to authenticate to a host other than the authenticating one, which is the case for reflection.
Old challenges are removed from the challenge table based on their age; that is, the amount of time passed since a particular challenge was added to the cache. Challenges older than 300 seconds are removed from the table via a deletion function. This function is triggered each time a challenge is added to the cache, meaning a deliberately failed authentication attempt will do nicely to flush old challenges.
The question then becomes one of connection logistics - are we able to keep a partially authenticated SMB connection open long enough to flush the associated challenge from the cache? Turns out that it is indeed possible, and a few additional lines to ntlmrelayx.py are all that is needed to bring back NTLM reflection.
Here’s a demo of notepad.exe authenticating to the host, Mallory, via WebDAV. NTLM messages are reflected back to the host’s SMB server, up until the final AUTHENTICATE message. At this point, the patched version of ntlmrelayx.py will wait for a period of 5 minutes and 15 seconds, after which the tool will attempt a second authentication with a bogus password. The challenge cache is flushed of the active challenge, and the final AUTHENTICATE message can be reflected to complete the attack.
As local authentication is employed, the level of access of the resulting session depends on the properties of the victim’s client process, specifically its security token.
For example, access to the
C$ hidden share (the easiest way to drop a RAT, for instance) is limited to the following conditions, which must all be met:
- User must be a member of the local Administrators group
- User must be a member of the Backup Operators group
- Token must be elevated
In the case of processes running under a domain administrator’s interactive session (in the example above), these conditions are trivially met by default by any medium integrity process that can be coerced into performing NTLM auth with Mallory.
Reflecting WebDAV authentication from a process running as medium integrity or higher, as a standard domain user, will provide write access to his or her home directory, provided the Users share is enabled.
Proof of Concept
The following PoC is a modification of
ntlmrelayx.py that implements the Ghost Potato attack:
The following researchers whose work this bug is reliant on:
- Issue reported to MSRC
- Issue reproduced by Microsoft engineers
- Microsoft resolve to release the patch as part of the November security update
- Patch released to public (CVE-2019-1384)