How to Detect Pass-the-Ticket Attacks

How to Detect Pass-the-Ticket Attacks

In our first post of the series, we looked at some interesting ways to detect the pass-the-hash attack. Pass-the-hash is an effective approach for exploiting NTLM authentication within an Active Directory domain. Pass-the-ticket is an alternate approach which leverages Kerberos authentication to perform lateral movement. 

In this post we will dive into how this attack works and what you can do to detect it.

How Pass-the-Ticket Works

In a pass-the-ticket attack, an attacker is able to extract a Kerberos Ticket Granting Ticket (TGT) from LSASS memory on a system and then use this on another system to request Kerberos service tickets (TGS) to gain access to network resources. 

One primary difference between pass-the-hash and pass-the-ticket, is that Kerberos TGT tickets expire (10 hours by default) whereas NTLM hashes only change when the user changes their password. So a TGT ticket must be used within its lifetime, or it can be renewed for a longer period of time (7 days).  

Tools like Mimikatz can be used to perform this attack, but in this post, I wanted to show another security tool Rubeus. Rubeus is a C# toolset written by harmj0y that lets you perform Kerberos based attacks and is based on the Kekeo project by Benjamin Delpy, the author of Mimikatz.

Let’s see a pass-the-ticket attack in action…

Extracting the TGT

To perform a pass-the-ticket attack with Rubeus the first step is to obtain a TGT.  TGTs and NTLM hashes may or may not be stored on a system after a user logs off based on security settings. One of the fun/scary features of Rubeus is a feature called “monitor” which will look for 4624 logon events and dump the TGT data for any new logon sessions on a system. 

If I use this command Rubeus will start monitoring for logon sessions every 30 seconds:

Rubeus.exe monitor /interval:30

Now, if anybody logs onto this system I will obtain their TGT.  To simulate that, I will just run a command as a user.

Runas /user:[domain\username] cmd.exe

Within 30 seconds, Rubeus will detect this logon and obtain the TGT for this user, and output it as a base64 encoded string.

You can then copy this into a text editor like Sublime Text and use regular expression to remove line breaks and spaces (‘\n      ‘).

Passing the Ticket

Now that we have the ticket, let’s use it before it expires!  To use this we will stick with Rubeus but this time use the ptt command

Rubeus.exe ptt /ticket:[Base64 string goes here]

You can see I have a TGT for the compromised user loaded into my session, and I can now use this to request TGS service tickets to access network resources as this user.  Success!

With that, let’s talk about what you can do to detect pass-the-ticket in your environment.

Detecting Pass-the-Ticket at the Endpoint

In researching detection of pass-the-ticket I came across a very interesting approach posted by a researcher Eyal Neemany at Javelin Networks.  The concept of this approach is to do the following at any endpoint in your environment when you want to investigate for pass-the-ticket activity:

  1. Look at the current logon sessions on that system
  2. Use the klist command to inspect the Kerberos tickets associated with that session
  3. Look for Kerberos tickets that do not match the user associated with the session.  If found, that means those were injected into memory and a pass-the-ticket attack is afoot. 

That’s a really clever way to look for this activity, but let’s take a deeper look to see how the pieces fit together.

First, we need to output all of the logon sessions, and obtain the logon IDs (in hex format) so we can pass them to the klist command to inspect their Kerberos tickets.  I used this script adapted from the Get-LoggedOnUsers function I found on GitHub to do the trick. 

        $regexa = '.+Domain="(.+)",Name="(.+)"$'
        $regexd = '.+LogonId="(\d+)"$'
        $logon_users = @(Get-WmiObject win32_loggedonuser -ComputerName 'localhost')

        $session_user = @{}
        $logon_users |% {
            $_.antecedent -match $regexa > $nul
            $username = $matches[1] + "\" + $matches[2]
            $_.dependent -match $regexd > $nul
            $session = $matches[1]
            $sessionHex = ('0x{0:X}' -f [int]$session)
            $session_user[$sessionHex] += $username 

This will output a list of sessions.  Then we can use the klist –li command and pass in a session ID to see the tickets associated with it. 

You can see by doing that here I can inspect a session for the user Michael, but see a Kerberos TGT for the user Gene. Pass-the-ticket detected! 

This worked reliably in my lab without false positives, but leave a comment if you know of any ways that this can be triggered by activity other than pass-the-ticket. 

Detecting Pass-the-Ticket at the Domain

Let’s say you didn’t want to run that logic on every endpoint, is there a way to look for pass-the-ticket behavior on your domain controllers?  There is!  It may not be quite as reliable, but it’s always good to have a detection you can get from your DC logs. 

To understand what to look for, let’s evaluate normal event log behavior you would see for Kerberos authentication on the network.

4768 – A Kerberos authentication ticket (TGT) was requested

The first event you should see is a 4768 event.  This is the TGT request and is the first thing that must happen for a user to leverage Kerberos to access a network resource.  You will get one of these for each user for every endpoint they access your domain from.  If a user account logs in from two separate workstations, they will request a TGT from each.

The most relevant information in this event is the user who requested the TGT and the computer they requested it from.

4769 – A Kerberos service ticket was requested

The next step in Kerberos is for the user to use that TGT and request a TGS service ticket to access a service on a computer, such as CIFS to get to a file share. This will also show up in the logs in event 4769 and you can see here the user who requested the ticket and the source computer.

4770 – A Kerberos service ticket was renewed

Renewing a TGT generates event 4770. By default, TGTs can be renewed for 7 days.  If you want to test this, Rubeus has a command “renew” to renew TGTs that have been extracted. You can see also in this ticket, you get the user who renewed and the source of the renewal. 

So to look for pass-the-ticket activity, what are you looking for that would stand out? Well if you assume the attacker will harvest TGTs and then use them on a different system, which is likely, then you can look for behavior that fits this pattern. 

That would be TGS requests or TGT renewals with a particular Account / Client pair that have no associated TGT request from that Account / Client pair. You would have to look at a TGS request or TGT renewal and then scan back the previous 10 hours to see if there was a TGT request that matches that user and computer. 

That is because in pass-the-ticket the attacker will never request a TGT, they will always steal it from LSASS. They may renew it, and they definitely may use it to request TGS service tickets. 

Now, that detection goes above and beyond event log filtering and to do this at scale likely requires the use of a SIEM or third-party product. If you’re looking for a way to detect this, check out StealthDEFEND and see how it can help with this and other Active Directory attacks. 

Interested in getting notifications for other blogs in this series? Sign up here.

Then, register for our upcoming webinar on AD lateral movement to get all the details!

6 thoughts on “How to Detect Pass-the-Ticket Attacks

  1. Great write up on this. Was curious if this would generate lots of false positive for things like constrained delegation scenarios.

    1. Hey John, I have definitely seen false positives when evaluating the Kerberos tickets as described in the “Detecting Pass-the-Ticket at the Endpoint” section. I’ve seen this from mounted drives and other common use cases. I could definitely see this triggering false positives in delegation scenarios, as I covered that topic with unconstrained delegation on computers and how this can result in TGTs being stored for users who connect over the network, that would likely trigger this false positive. However, I haven’t tested that yet. I would still expect the domain based detections to be accurate. If I get some time I would love to test out some of those delegation scenarios and how it affects reliable pass-the-ticket detection. Thanks for the comment, and if you have any experience from your own testing on this I’d love to learn from it.

  2. Hello Jeff,

    It seems that the Klist -li command does not work for me.
    I tried it with all types of logon id I created and the same result occur,

    PS C:\Windows\system32> klist -li 0xF53F133FA

    Current LogonId is 0xf:0x53f133fa
    Targeted LogonId is 0:0x7fffffff
    Error calling API LsaCallAuthenticationPackage (ShowTickets substatus): 1312

    klist failed with 0xc000005f/-1073741729: A specified logon session does not exist. It may already have been terminated.

    Can you guess why such a logon id isn’t recognized although it is running?


  3. NM – To answer my question,

    The Klist is asking for the hex suffix

    0xf OR 0

    So I had to use the entire string,

    silly me.

  4. Hi
    I have a question ,
    If TGT can be renew for 7 days , the First TGT request can be 7 days ago and since than only TGT renew requests .
    so if I receive renew tgt event and i look only 10 ahours ago i might miss the TGT request , and that will be cause a false positive.

    how you handle this situation ?

    1. You are right. If you see a TGT Renewal event (4770) you could either see another TGT Renewal previously, or a TGT request (4769). If you are running this detection, the first time you see a TGT Renewal and look back 10 hours (or whatever your Maximum lifetime for service ticket is configured to), you would see no TGT Request (4769) and the detection would trigger.

      You are correct in that if you wanted to run this in a more point-and-shoot manner on a system to profile it for PTT activity, you would have to reconcile 7 days (or whatever your Maximum lifetime for user ticket renewal is configured to) to look for the anomalies.

      So it really depends on how you’re doing your detection, in real time or more of a snapshot based approach. But thanks for the comment I think that is definitely a point worth clarifying!

Leave a Reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.

Start a Free Stealthbits Trial!

No risk. No obligation.

Privacy Preference Center