Skip to main content

ESC17 - Beyond WSUS: RDP Interception and NLA Bypass

Published on 17 min read

A research series by Alexis MARTIN (qu35t) and Alexis PARET (nnino). Every technique in this series was reproduced end to end in a fully patched lab environment.

The previous parts applied the ESC17 primitive to internal HTTPS and to LDAP. The principle stays the same: ESC17 manufactures a domain-trusted TLS server identity, for any hostname. This part applies it to RDP, the remote administration protocol par excellence, the one administrators use every day to connect to servers with often-privileged accounts.

RDP is a peculiar target, because the trusted certificate changes everything. Man-in-the-middle attacks on RDP have existed for a long time, but they usually run into a simple obstacle: the mstsc client shows a certificate warning, and a savvy administrator refuses the connection. ESC17 removes that obstacle. Since the certificate presented is genuinely issued by the domain CA, no warning is raised, and the interception becomes transparent.

A word on RDP security

To understand what is exploitable, you have to know how an RDP session negotiates its security. At the very start of the connection, in the X.224 exchange, the client advertises the security layers it accepts. Three options coexist.

  • Standard RDP Security. The legacy encryption, based on RC4. Authentication and the session are encrypted with a key derived from a server random. The Client Info PDU, which carries the credentials, is encrypted with RC4.
  • TLS (SSL). The RDP session is wrapped in a TLS tunnel. The server presents a certificate, and this is where ESC17 comes in. Once TLS is established, the Client Info PDU flows in the clear inside the tunnel.
  • CredSSP (HYBRID), i.e. NLA. Network Level Authentication authenticates the user before opening the graphical session, via the CredSSP protocol. Crucially, CredSSP ties the authentication to the server certificate’s public key (the pubKeyAuth field), precisely to detect a man-in-the-middle.

A normal connection chains these elements as follows:

Normal RDP negotiation: X.224, TLS handshake with the server certificate, then the Client Info PDU

It is these three modes, and the way they are negotiated, that decide what an attacker can extract.

The certificate that no longer raises a warning

This is ESC17’s direct contribution to RDP. A classic RDP interception tool has to present a certificate it does not control, so a self-signed certificate or one issued by an authority unknown to the client. mstsc then shows a clear warning, and the cautious administrator stops right there.

With ESC17, you forge a certificate for the exact name of the RDP server, issued by the enterprise CA. The client validates it as it would validate the real one, without the slightest warning. The user sees their usual RDP window and enters their credentials with full confidence.

As with HTTPS, the certificate has to carry the right name. adecrypt generates it per hostname, from the server’s FQDN (resolved by PTR or supplied via --rdp-fqdn), and also supports a *.domain wildcard certificate to cover several servers in a single issuance, with the same stealth benefit described in the HTTPS part.

The NLA downgrade, and why it no longer works

The historic approach against NLA is the downgrade. In the X.224 exchange that opens the connection, the client advertises the layers it accepts, generally SSL and HYBRID (CredSSP). adecrypt rewrites that advertisement to keep only SSL and drop HYBRID. If the switch goes through, authentication happens inside the session, and the Client Info PDU, which carries the password, flows in the clear inside the TLS tunnel we hold.

NLA downgrade: adecrypt strips HYBRID from the X.224 negotiation, forces SSL only, and captures the Client Info PDU in the clear

In theory, the approach is straightforward. In practice, on an up-to-date fleet, it fails, and the important point is that the block comes from the client, not the server.

Take the most attacker-favorable case, a server whose NLA is disabled.

Terminal
nxc rdp 10.10.20.11
RDP 10.10.20.11 3389 WK01 [*] Windows 10 / Server 2016 Build 19041 (name:WK01) (domain:esc17.local) (nla:False)
Terminal
[RDP] 10.10.20.12 : client requests protocols=0x0B (SSL=1 HYBRID=1 HYBRID_EX=1)
[RDP] 10.10.20.12 : server -> SSL accepted ; TLS handshake with client (cert ESC17)
[RDP] 10.10.20.12 : TLS established (client=TLSv1.2, server=TLSv1.2) - capturing Client Info PDU

The server accepts SSL, TLS establishes with our certificate, everything looks set for the capture. And yet no Client Info PDU arrives, the connection breaks off, and the victim gets an authentication error.

mstsc error, an authentication error has occurred (Code: 0x609)

The cause is entirely on the client side. A modern mstsc offered CredSSP (HYBRID) and refuses to do without it. Since the 2018 patches (CredSSP Encryption Oracle Remediation, set by default to “Force Updated Clients”), an up-to-date client no longer accepts falling back to an SSL session without CredSSP, even when the server would accept it. It cuts the connection with the code 0x609 rather than send its credentials in a session not protected by CredSSP. Disabling NLA on the server therefore changes nothing, it is the client that locks it down.

The second block is on the server side. If it enforces NLA, it refuses the switch at the negotiation stage.

Terminal
nxc rdp 10.10.20.11
RDP 10.10.20.11 3389 WK01 [*] Windows 10 / Server 2016 Build 19041 (name:WK01) (domain:esc17.local) (nla:True)
Terminal
[RDP] 10.10.20.12 : client requests protocols=0x0B (SSL=1 HYBRID=1 HYBRID_EX=1)
[RDP] 10.10.20.12 : downgrade refused (server enforces NLA/HYBRID, proto=5) - pubKeyAuth = wall

The server returns a negotiation failure proto=5 (HYBRID_REQUIRED_BY_SERVER), and the victim sees an explicit error.

mstsc error, The remote computer requires Network Level Authentication

Relaying the CredSSP authentication rather than downgrading it fares no better, and it is the RDP counterpart of the channel binding seen in the LDAP part. CredSSP includes the server certificate’s public key in its exchange, the pubKeyAuth field. A man-in-the-middle that terminates TLS and then reopens another one to the real server presents a public key different from the expected one, and the check fails.

The takeaway is therefore unfavorable to the classic approach. Against a modern, up-to-date client, the downgrade fails whatever the server’s configuration. It stays exploitable only against old, unpatched clients, or ones whose EOR policy has been weakened, as well as against sessions in Standard RDP Security (RC4), whose Client Info PDU is encrypted differently and is not extractable as-is anyway.

Should we conclude that NLA closes the door for good? No. One route remains, specific to ESC17, that does not downgrade but terminates CredSSP. That is the subject of the next section.

Breaking the lock: terminating CredSSP yourself

The previous block rests on solid reasoning, but it contains an implicit assumption, that the attacker cannot produce a valid pubKeyAuth. Let’s first clarify what this field actually binds. Contrary to a widespread intuition, it does not bind the certificate’s public key to the TLS private key, but to the session key of the NTLM authentication, derived from the user’s secret. Owning the ESC17 certificate and its private key is therefore not enough, a classic relay still fails for lack of that session key.

The question then becomes, can this session key be obtained other than through the victim’s password? Yes, by controlling a machine account in the domain. Any domain member can ask the domain controller to validate a network authentication, via the Netlogon secure channel, and picks up in passing the session key the DC computes. This is the pivot of the technique we implemented in adecrypt.

Terminating CredSSP: adecrypt plays the NTLM server, obtains the session key from the DC via Netlogon pass-through, forges a valid pubKeyAuth, and recovers the TSPasswordCreds in the clear despite enforced NLA

The step-by-step:

  1. The client connects, adecrypt accepts NLA (it does not downgrade) and terminates TLS with the ESC17 certificate.
  2. adecrypt plays the role of the NTLM server. It sends its own challenge, then receives the client’s NTLMv2 response (Type 3 message), along with the pubKeyAuth and the client nonce.
  3. It lacks the session key to validate this exchange and respond. It asks the DC for it, presenting its challenge and the client’s response in a Netlogon pass-through authenticated by a machine account it controls. The DC validates and returns the session key.
  4. adecrypt derives the session key, forges a valid server pubKeyAuth, and sends it back to the client.
  5. With pubKeyAuth validated, the client sends the last CredSSP message, its delegated credentials (TSPasswordCreds), that is, its cleartext password.

What ESC17 brings is twofold. The trusted certificate terminates TLS with no warning, and its private key makes the attacker a credible CredSSP server. The machine account, for its part, unlocks the session key. Neither of these elements alone is enough, it is their combination that opens the way.

One point is worth stressing, because it is what separates this approach from the downgrade. The downgrade tried to make CredSSP be abandoned, so it depended directly on the NLA setting: blocked if the client refuses to do without it, blocked if the server enforces it at the negotiation stage. The termination never tries to do without it. adecrypt accepts CredSSP and plays it all the way through in place of the server. Whether the server enforces NLA or has disabled it then changes nothing, because the authentication never reaches the real server, it terminates at the attacker, and the client gets exactly the CredSSP it demands. That is why the same command captures the password in both cases, with or without NLA on the server. The demonstration that follows deliberately targets the server with NLA enforced, the one that blocked all the previous approaches.

Direct NTLM, or the detour through Kerberos

The sequence above corresponds to a client that speaks NTLM from the outset, which is the case when you connect by IP address: no name, no SPN, hence no Kerberos. CredSSP then carries NTLM directly and the five steps chain without a detour. This is the simplest case, and it already captures the cleartext password.

RDP connection by IP address to 10.10.20.11, account ESC17\Administrator

Connection by IP address: with no SPN, the client cannot do Kerberos and goes straight to NTLM.

Terminal
sudo python3 adecrypt.py rdp --targets 10.10.20.12 --gateway 10.10.20.1 \
--interface eth0 --target 10.10.20.11 --pfx ../wk01.pfx \
--credssp-terminate --machine-account 'pwned$' --machine-pass '...' \
--dc-ip 10.10.20.10 --dc-name DC01 --domain esc17.local
[RDP] 10.10.20.12 -> 10.10.20.11
[RDP] 10.10.20.12 : client requests protocols=0x0B (SSL=1 HYBRID=1 HYBRID_EX=1)
[RDP] 10.10.20.12 : T1 - HYBRID accepted (impersonating server), TLS with ESC17 cert
[T1] PublicKey (PKCS#1 DER) 270 bytes
[T1] CredSSP v=6 SPNEGO=False (optimistic mechToken = NTLM Type1, no downgrade)
[T1] Type2 (CHALLENGE) sent, serverChallenge=e165aea7abb71744
[T1] Type3 (AUTHENTICATE) received : ESC17\Administrator wks=WK02 KEY_EXCH=1 mechListMIC=no
[T1][NRPC] alter bind (ALTER_CONTEXT) OK, AES-sealed channel
[T1][NRPC] NetrLogonSamLogonEx (sealed) -> ESC17\Administrator
[T1][NRPC] UserSessionKey (SessionBaseKey, cleartext) = ab66d828b910951db53ca249e2daae26
[T1] pubKeyAuth client MAC_ok=True (seq=0), binding_match=True
[T1] server pubKeyAuth sent (seq 0)
[T1] authInfo received and unsealed (seq 1, MAC_ok=True)
>>> [RDP-CREDSSP] ESC17\Administrator : Password1! (host=10.10.20.12)

The administrator’s password comes out in the clear. Note in passing that pubKeyAuth and authInfo here carry sequence numbers 0 and 1, without the one-step offset introduced by the mechListMIC of the Kerberos detour seen further down.

On the client side, however, the connection does not complete: mstsc shows “An internal error has occurred.” Terminating CredSSP only serves to recover the delegated credentials, without making us a real RDP server able to then open the graphical session. The exchange therefore stops right after the delegation.

An internal error has occurred, shown by mstsc on the client side after the capture

Victim side, the connection fails after the capture: adecrypt terminates CredSSP but does not open the graphical session.

So that the user is not left stuck, adecrypt remembers the already-captured source and switches its next attempt to a transparent relay to the real server. Whoever clicks again then gets a normal RDP session, without retyping anything, the capture having already taken place.

Terminal
[RDP] 10.10.20.12 -> 10.10.20.11
[RDP] 10.10.20.12 : client requests protocols=0x0B (SSL=1 HYBRID=1 HYBRID_EX=1)
[RDP] 10.10.20.12 : already captured -> transparent relay (normal session)

More generally, the victim connects by hostname, and there the client tries Kerberos first: it holds a service ticket for TERMSRV/wk01.esc17.local that the DC issued to it, regardless of who actually answers at that address. Kerberos does not lend itself to our technique, its ticket is encrypted for the key of the targeted computer account and the Netlogon pass-through only validates NTLM. We therefore have to bring the client back to NTLM.

RDP connection by hostname wk01.esc17.local, account ESC17\Administrator

Connection by hostname: the client tries Kerberos first, which adecrypt then brings back to NTLM in the SPNEGO negotiation.

This is possible because, in its SPNEGO negotiation, the client offers Kerberos and NTLM. adecrypt responds by explicitly selecting NTLM. Selecting a mechanism that is not the client’s preferred one triggers, as SPNEGO requires, a mechListMIC exchange meant to protect the integrity of the negotiation against a malicious downgrade. adecrypt checks the client’s mechListMIC then provides its own, which it can do since it now holds the session key obtained from the DC. Once the negotiation is brought back to NTLM and its integrity satisfied, the rest is strictly identical, pubKeyAuth then credential delegation.

The result, by hostname, against the same server with NLA enforced:

Terminal
sudo python3 adecrypt.py rdp --targets 10.10.20.12 --gateway 10.10.20.1 \
--interface eth0 --target 10.10.20.11 --pfx ../wk01.pfx \
--credssp-terminate --machine-account 'pwned$' --machine-pass '...' \
--dc-ip 10.10.20.10 --dc-name DC01 --domain esc17.local
[RDP] 10.10.20.12 : T1 - HYBRID accepted (impersonating server), TLS with ESC17 cert
[T1] SPNEGO negTokenInit mechTypes=['Kerberos', 'Kerberos', 'NEGOEX', 'NTLM'] (optimistic mechToken = AP-REQ Kerberos)
[T1] downgrade SPNEGO Kerberos->NTLM (negTokenResp request-mic, NTLM selection)
[T1] Type1 (NEGOTIATE) obtained (downgrade=True, MIC_required=True)
[T1] Type2 (CHALLENGE) sent, serverChallenge=3dd0b140b66e2913
[T1] Type3 (AUTHENTICATE) received : ESC17\Administrator wks=WK02 KEY_EXCH=1 mechListMIC=yes
[T1][NRPC] alter bind (ALTER_CONTEXT) OK, AES-sealed channel
[T1][NRPC] NetrLogonSamLogonEx (sealed) -> ESC17\Administrator
[T1][NRPC] UserSessionKey (SessionBaseKey, cleartext) = 2fc9b4153c8bdd9881c171d41552bdad
[T1] mechListMIC client (seq 0) MAC_ok=True
[T1] SPNEGO completed (accept-completed + server mechListMIC), waiting for client pubKeyAuth
[T1] pubKeyAuth client MAC_ok=True (seq=1), binding_match=True
[T1] server pubKeyAuth sent (seq 1)
[T1] authInfo received and unsealed (seq 2, MAC_ok=True)
>>> [RDP-CREDSSP] ESC17\Administrator : Password1! (host=10.10.20.12)

An administrator password, in the clear, by hostname, against a server with NLA enforced and a modern, up-to-date Windows client, where both the downgrade and the relay failed.

What is reused, what is new

This chain does not come out of nowhere, and we should credit what it builds on. The SPNEGO downgrade to NTLM, terminating CredSSP as a server, and the idea of recovering the session key from the DC with a machine account, were described as early as 2020 by Bertoli and Bourguenolle in their CredSSPy work (SSTIC 2020), which extended ANSSI’s 2012 RDP analysis. The primitive “a machine account obtains an NTLM session key from the DC” is, moreover, known as Your Session Key is My Session Key (CVE-2019-1019).

Two points remained open, however, and that is this article’s contribution. First, CredSSPy explicitly named the blocker: a machine account can obtain an RDP certificate signed by the CA, but not in the name of the target server, so the client always shows a warning. This is precisely what ESC17 removes, a certificate trusted for the exact FQDN of the target. Second, the public state of the art stopped at capturing a NetNTLMv2, to crack or relay, and considered the rest impossible against an enforced NLA. By forging a valid pubKeyAuth from the session key obtained, we carry the exchange all the way to the delegation and recover the cleartext password.

A final word on the CVE-2019-1019 hardening, whose fix binds the session key to the machine account that opened the Netlogon channel. This technique does not conflict with it, because it does not relay the authentication to another server, it terminates it in its own name as a machine account. The DC’s validation therefore stays consistent, as confirmed by the test on an up-to-date DC, which already enforces sealing of the Netlogon channel.

What defeats the attack

Effective as it is against an enforced NLA, this technique rests on two preconditions, and it falls apart as soon as either one disappears.

First, it needs a machine account in the domain, trivial to obtain when MachineAccountQuota is greater than zero (its default value). This account is what queries the DC to obtain the session key. The Netlogon hardening (the remediation of CVE-2020-1472), on the other hand, does nothing here, the pass-through uses a perfectly normal signed and sealed channel.

Second and above all, it needs NTLM to stay usable and a reusable secret to be delegated in the session. This is precisely what the SPNEGO detour goes after, by bringing Kerberos back to NTLM. Anything that cuts this precondition closes the attack:

  • NTLM disabled in the domain. Without NTLM, the Netlogon pass-through has nothing to validate anymore, and the SPNEGO detour has no mechanism to fall back on.
  • Protected Users. Placing sensitive accounts (all the more so administrators) in this group flatly forbids them NTLM and blocks the cleartext credential delegation by CredSSP. A member of Protected Users therefore cannot be captured this way.
  • Remote Credential Guard and Restricted Admin. These are two RDP connection modes, distinct from Protected Users, that avoid sending a reusable secret to the target server. Restricted Admin transmits no credentials at all (the session opens via a network logon, with no password or delegation), and Remote Credential Guard keeps the credentials on the client machine by redirecting the authentication requests. In both cases, there is no longer any cleartext TSPasswordCreds to intercept. In exchange, they open other surfaces (pass-the-hash-style replay for Restricted Admin), which remains a trade-off to make depending on the environment.
  • Smart card authentication, which transits no reusable password.

To place the two notions that are often confused: Protected Users is an Active Directory group that hardens the account itself (no NTLM, no cleartext CredSSP delegation, Kerberos AES only), whereas Restricted Admin and Remote Credential Guard are connection modes you choose when opening the RDP session. The former protect the account everywhere, the latter protect a given connection.

Beyond the password

Credential capture is only one of the possibilities the man-in-the-middle position opens up. Once at the heart of an RDP’s TLS session, you do not just see the password, you see the whole session: the screen sent to the victim, their keystrokes, the clipboard, and the redirected devices. You can also inject input into the session, often an administrator’s.

adecrypt focuses on credential capture, which is the most direct and most reusable objective. Full observation and manipulation of the session is a capability of the position, not a function of the tool, but they are worth keeping in mind in the risk assessment.

Recap

The table sums up the two approaches against RDP, once positioned as a man-in-the-middle with the ESC17 certificate.

ApproachWhat you getPreconditionsOutcome
NLA downgradenothing on a modern clientthe client accepts an SSL session without CredSSPfails on an up-to-date client (error 0x609) as well as on a server that enforces NLA; only remains exploitable against old or unpatched clients
CredSSP termination (--credssp-terminate)the cleartext password (TSPasswordCreds)ESC17 certificate for the right FQDN, a domain machine account, NTLM usable and a reusable secret delegatedreusable password, including against a server that enforces NLA and a modern client (by IP over direct NTLM, by hostname via SPNEGO Kerberos → NTLM downgrade)

What closes the attack: NTLM disabled, accounts in Protected Users, connections in Restricted Admin or Remote Credential Guard, or smart-card authentication, all cases where no reusable secret is delegated.

Mitigations

  • Enforce NLA on both sides. Require CredSSP on the server side (the “Allow connections only from computers running Remote Desktop with Network Level Authentication” option) and on the client side. This is what closes the classic downgrade, but recall that it is not enough against CredSSP termination, which only needs a machine account and NTLM.
  • Disable or restrict NTLM in the domain, and prefer Remote Credential Guard or Restricted Admin for administrative connections, so that no reusable password transits.
  • Fix the ESC17 template at the source, by removing the “supply in the request” flag and restricting enrollment rights, which deprives the attacker of the trusted certificate that makes the attack silent.
  • Segment the network to reduce exploitable man-in-the-middle positions.

Actions

Switch languageOpen the RSS feed

Go to

HomeBlogTagsArchives