Windows API to get information about cached Kerberos tickets

5.9k Views Asked by At

I understand that I can get what I need by running "klist.exe" and parsing the output, but I'm wondering if there is a Windows/C#/Powershell API to get information about cached Kerberos tickets on Windows server.

2

There are 2 best solutions below

0
On

So far I was able to find source code for klist.exe and "LsaCallAuthenticationPackage" seems to be the way to communicate with Kerberos cache in Windows:

https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/security/authorization/klist/KList.c

Status = LsaCallAuthenticationPackage(
            LogonHandle,
            PackageId,
            &CacheRequest,
            sizeof(CacheRequest),
            (PVOID *) &CacheResponse,
            &ResponseSize,
            &SubStatus
            );
if (!SEC_SUCCESS(Status) || !SEC_SUCCESS(SubStatus))
{
    ShowNTError("LsaCallAuthenticationPackage", Status);
    printf("Substatus: 0x%x\n",SubStatus);
    return FALSE;
}

printf("\nCached Tickets: (%lu)\n", CacheResponse->CountOfTickets);
for (Index = 0; Index < CacheResponse->CountOfTickets ; Index++ )
{
    printf("\n   Server: %wZ@%wZ\n",
        &CacheResponse->Tickets[Index].ServerName,
        &CacheResponse->Tickets[Index].RealmName);
0
On

Microsoft already provides a set of scripts for this. So, you don't have to write this from scratch. Viewing and Purging Cached Kerberos Tickets and yes they have klist in the mix. Otherwise, you end up trying to leverage …

[System.Security.Principal.WindowsIdentity]

… and then doing SID translations and the like or you end up going down the same discussion in this Q&A.

How to programmatically clear the Kerberos ticket cache

Or leveraging these resources and tweaking as needed.

Kerberos Module The module gives access to the Kerberos ticket cache. It can read and purge tickets of the current logon session.

A Managed Code validator for Kerberos tickets

The Point of Kerberos.NET is to make Kerberos much easier to work with in such scenarios. This is done by removing any hard dependencies on Windows and moving all ticket processing to the application itself. This of course means you don't need the application to be on a domain-joined machine, and it probably doesn't need to be on Windows either.

Install-Package Kerberos.NET

Using the Library

Ticket authentication occurs in two stages. The first stage validates the ticket for correctness via an IKerberosValidator with a default implementation of KerberosValidator. The second stage involves converting the ticket in to a usable ClaimsIdentity, which occurs in the KerberosAuthenticator.

The easiest way to get started is to create a new KerberosAuthenticator and calling Authenticate. If you need to tweak the behavior of the conversion, you can do so by overriding the ConvertTicket(DecryptedData data) method.

var authenticator = new KerberosAuthenticator(new KeyTable(File.ReadAllBytes("sample.keytab")));

var identity = authenticator.Authenticate("YIIHCAYGKwYBBQUCoIIG...");

Assert.IsNotNull(identity);

var name = identity.Name;

Assert.IsFalse(string.IsNullOrWhitespace(name));

Note that the constructor parameter for the authenticator is a KeyTable. The KeyTable is a common format used to store keys on other platforms. You can either use a file created by a tool like ktpass, or you can just pass a KerberosKey during instantiation and it'll have the same effect.

List All Cached Kerberos Tickets

When administering or troubleshooting authentication in a domain there are times when you need to know whether a ticket for a user and service are cached on a computer. This script exports all user's cached tickets on a computer to a text file for review.

Download : GetKerbTix.ps1

Purge All Kerberos Tickets

There are situations where an administrator may want to clear the cached Kerberos tickets on a server. For example, user Bob left the company. In situations like that you can run this script to clear all cached Kerberos tickets and TGTs for all sessions on the computer.

Download : PurgeAllKerbTickets.ps1

#************************************************ 
# GetKerbTix.ps1 
# Version 1.0 
# Date: 6-11-2014 
# Author: Tim Springston [MSFT] 
# Description: On a specific computer the script is ran on,  
#  this script finds all logon sessions which have Kerberos 
#     tickets cached and enumerates the tickets and any ticket granting tickets. 
# The tickets may be from remote or interactive users and may be  
#  any logon type session (network, batch, interactive, remote interactive...). 
# This script will run on Windows Server 2008/Vista and later. 
#************************************************ 
cls 
$FormatEnumerationLimit = -1 
$ComputerName = $env:COMPUTERNAME 
$UserName = [Security.Principal.WindowsIdentity]::GetCurrent().name 
$ComputerDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().name 
$Date = Get-Date 


#Prepare an output file to place info into. 
$ExportFile = "C:\windows\temp\" + $ComputerName + "_CachedKerberosTickets.txt" 
"Cached Kerberos Tickets" | Out-File $ExportFile -Encoding utf8 
"Logged on User:$UserName" | Out-File $ExportFile -Append -Encoding utf8 
"Computer name: $ComputerName" | Out-File $ExportFile -Append -Encoding utf8 
"Computer Domain: $ComputerDomain" | Out-File $ExportFile -Append -Encoding utf8 
"Date: $Date" | Out-File $ExportFile -Append -Encoding utf8 
"************************************" | Out-File $ExportFile -Append -Encoding utf8 

function GetKerbSessions 
    { 
    $Sessions = @() 
    $WMILogonSessions = gwmi win32_LogonSession 
    foreach ($WMILogonSession in $WMILogonSessions) 
        { 
        $LUID = [Convert]::ToString($WMILogonSession.LogonID, 16) 
        $LUID = '0x' + $LUID 
        $Sessions += $LUID 
        } 
    return $sessions 
    } 

function GetKerbSessionInfo 
    { 
    $OS = gwmi win32_operatingsystem 
    $sessions = New-Object PSObject 
    if ($OS.Buildnumber -ge 9200) 
        { 
        $KlistSessions = klist sessions 
        $Counter = 0 

        foreach ($item in $KlistSessions) 
            { 
            if ($item -match "^\[.*\]") 
                { 
                $LogonId = $item.split(' ')[3] 
                $LogonId = $LogonId.Replace('0:','') 
                $Identity = $item.split(' ')[4] 
                $Token5 = $item.Split(' ')[5] 
                $AuthnMethod = $Token5.Split(':')[0] 
                $LogonType = $Token5.Split(':')[1] 
                $Session = New-Object PSObject 
                Add-Member -InputObject $Session -MemberType NoteProperty -Name "SessionID" -Value $LogonId 
                Add-Member -InputObject $Session -MemberType NoteProperty -Name "Identity" -Value $Identity 
                Add-Member -InputObject $Session -MemberType NoteProperty -Name "Authentication Method" -Value $AuthnMethod             
                Add-Member -InputObject $Session -MemberType NoteProperty -Name "Logon Type" -Value $LogonType 

                Add-Member -InputObject $sessions -MemberType NoteProperty -Name $LogonId -Value $Session 
                $Session = $null 
                } 
            } 
        } 
    if ($OS.Buildnumber -lt 9200) 
        { 
        $WMILogonSessions = gwmi win32_LogonSession 
        foreach ($WMILogonSession in $WMILogonSessions) 
            { 
            $LUID = [Convert]::ToString($WMILogonSession.LogonID, 16) 
            $LUID = '0x' + $LUID 
            $Session = New-Object PSObject 
            Add-Member -InputObject $Session -MemberType NoteProperty -Name "SessionID" -Value $LUID 
            Add-Member -InputObject $Session -MemberType NoteProperty -Name "Identity" -Value "Not available" 
            Add-Member -InputObject $Session -MemberType NoteProperty -Name "Authentication Method" -Value $WMILogonSession.AuthenticationPackage         
            Add-Member -InputObject $Session -MemberType NoteProperty -Name "Logon Type" -Value $WMILogonSession.LogonType 

            Add-Member -InputObject $sessions -MemberType NoteProperty -Name $LUID -Value $Session 
            $Session = $null 
            } 
        } 
    return $sessions 
    } 

function ReturnSessionTGTs 
    { 
    param ($SessionID = $null) 
    if ($SessionID -eq $null) 
        { 
        $RawTGT =  klist.exe tgt 
        } 
        else 
            { 
            $RawTGT =  klist.exe tgt -li $sessionID 
            } 
    $TGT = @() 
    foreach ($Line in $RawTGT) 
        { 
        if ($Line.length -ge 1) 
            { 
            $TGT += $Line 
            } 
        } 
    if ($TGT -contains 'Error calling API LsaCallAuthenticationPackage (Ticket Granting Ticket substatus): 1312') 
        {$TGT = 'No ticket granting ticket cached in session.'} 
    return $TGT 
    }     

function ReturnSessionTickets  
    { 
    param ($SessionID = $null) 
    $OS = gwmi win32_operatingsystem 
    if ($SessionID -eq $null) 
        { 
        $TicketsArray =  klist.exe tickets 
        } 
        else 
            { 
            $TicketsArray =  klist.exe tickets -li $sessionID 
            } 
    $Counter = 0 
    $TicketsObject = New-Object PSObject 
    foreach ($line in $TicketsArray) 
        { 
        if ($line -match "^#\d") 
            { 
            $Ticket = New-Object PSObject 
            $Number = $Line.Split('>')[0] 
            $Line1 = $Line.Split('>')[1] 
            $TicketNumber = "Ticket " + $Number 
            $Client = $Line1 ;    $Client = $Client.Replace('Client:','') ; $Client = $Client.Substring(2) 
            $Server = $TicketsArray[$Counter+1]; $Server = $Server.Replace('Server:','') ;$Server = $Server.substring(2) 
            $KerbTicketEType = $TicketsArray[$Counter+2];$KerbTicketEType = $KerbTicketEType.Replace('KerbTicket Encryption Type:','');$KerbTicketEType = $KerbTicketEType.substring(2) 
            $TickFlags = $TicketsArray[$Counter+3];$TickFlags = $TickFlags.Replace('Ticket Flags','');$TickFlags = $TickFlags.substring(2) 
            $StartTime =  $TicketsArray[$Counter+4];$StartTime = $StartTime.Replace('Start Time:','');$StartTime = $StartTime.substring(2) 
            $EndTime = $TicketsArray[$Counter+5];$EndTime = $EndTime.Replace('End Time:','');$EndTime = $EndTime.substring(4) 
            $RenewTime = $TicketsArray[$Counter+6];$RenewTime = $RenewTime.Replace('Renew Time:','');$RenewTime = $RenewTime.substring(2) 
            $SessionKey = $TicketsArray[$Counter+7];$SessionKey = $SessionKey.Replace('Session Key Type:','');$SessionKey = $SessionKey.substring(2) 

            Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "Client" -Value $Client 
            Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "Server" -Value $Server 
            Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "KerbTicket Encryption Type" -Value $KerbTicketEType 
            Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "Ticket Flags" -Value $TickFlags 
            Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "Start Time" -Value $StartTime 
            Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "End Time" -Value $EndTime 
            Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "Renew Time" -Value $RenewTime 
            Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "Session Key Type" -Value $SessionKey 

            if ($OS.BuildNumber -ge 9200) 
                { 
                $CacheFlags =  $TicketsArray[$Counter+8];$CacheFlags = $CacheFlags.Replace('Cache Flags:','');$CacheFlags = $CacheFlags.substring(2) 
                $KDCCalled = $TicketsArray[$Counter+9];$KDCCalled = $KDCCalled.Replace('Kdc Called:','');$KDCCalled = $KDCCalled.substring(2) 
                Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "Cache Flags" -Value $CacheFlags 
                Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "KDC Called" -Value $KDCCalled 
                } 
            Add-Member -InputObject $TicketsObject -MemberType NoteProperty -Name $TicketNumber -Value $Ticket 
            $Ticket = $null 
            } 
        $Counter++ 


        } 
    return $TicketsObject 
    }     

$OS = gwmi win32_operatingsystem 
$sessions = getkerbsessions 
$sessioninfo = GetKerbSessionInfo 
foreach ($Session in $sessions) 
{     
    #Get Session details as well 
    $currentsessioninfo = $sessioninfo.$session 
    $ID = $currentsessioninfo.identity 
    $SessionID = $currentsessioninfo.SessionID 
    $LogonType = $currentsessioninfo.'Logon Type' 
    $AuthMethod = $currentsessioninfo.'Authentication Method' 
    if ($OS.Buildnumber -lt 9200) 
        { 
        Write-Host "Kerberos Tickets for LogonID $SessionID" 
        "Kerberos Tickets for LogonID $SessionID" | Out-File $ExportFile -Append -Encoding utf8 
        } 
        else 
        { 
        Write-Host "Kerberos Tickets for $ID" 
        "Kerberos Tickets for $ID" | Out-File $ExportFile -Append -Encoding utf8 
        } 
    Write-Host "*****************************" 
     "*****************************" | Out-File $ExportFile -Append -Encoding utf8 
    Write-Host "Logon Type: $LogonType" 
    "Logon Type: $LogonType" | Out-File $ExportFile -Append -Encoding utf8 
    Write-host "Session ID: $SessionID" 
    "Session ID: $SessionID" | Out-File $ExportFile -Append -Encoding utf8 
    Write-host "Auth Method: $AuthMethod" 
    "Auth Method: $AuthMethod" | Out-File $ExportFile -Append -Encoding utf8 
    $SessionTickets = ReturnSessionTickets $Session 


    $TGT = ReturnSessionTGTs $SessionID 
    $TGT | FL * 
    $TGT | Out-File $ExportFile -Append -Encoding utf8 

    if ($SessionTickets -notmatch 'Ticket') 
        { 
        Write-Host "Session TGT: No tickets for this session in cache." 
        "Session TGT: No tickets for this session in cache." | Out-File $ExportFile -Append -Encoding utf8 
        } 
        else 
        { 
        $SessionTickets | FL * 
        $SessionTickets    | Out-File $ExportFile -Append -Encoding utf8 
        } 
    Write-Host "`n" 
     "`n" | Out-File $ExportFile -Append -Encoding utf8 

} 


#************************************************
# PurgeAllKerbTickets.ps1
# Version 1.0
# Date: 6-12-2014
# Author: Tim Springston [MSFT]
# Description: On a specific computer the script is ran on, 
#  this script finds all logon sessions which have Kerberos
#  tickets cached and for each session purges the ticket granting
#   tickets and the tickets using klist.exe.
#************************************************
cls

function GetKerbSessions
    {
    $Sessions = @()
    $WMILogonSessions = gwmi win32_LogonSession
    foreach ($WMILogonSession in $WMILogonSessions)
        {
        $LUID = [Convert]::ToString($WMILogonSession.LogonID, 16)
        $LUID = '0x' + $LUID
        $Sessions += $LUID
        }
    return $sessions
    }

Write-Host "WARNING: This script will purge all cached Kerberos tickets on the local computer for all sessions (whether interactive, network or other sessions)."  -backgroundcolor Red 
Write-Host "In a well-connected environment clients will request and obtain Kerberos tickets on demand without interruption. If not well-connected to a domain controller (remote network) then further network resource authentication may fail or use NTLM if tickets are purged." -BackgroundColor red
Write-Host "Confirm whether to purge by entering YES"
$Response = Read-Host

if ($Response -match 'YES')
    {
    $sessions = GetKerbSessions

    foreach ($Session in $sessions)
        {
        $PurgedTix = klist.exe -li $Session purge
        }
    Write-Host "All tickets purged!" -backgroundcolor green
    }
    else
        {
        Write-Host "Confirmation not received. NOT purging tickets." -backgroundcolor yellow
        }