Reputation: 1492
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.
Upvotes: 1
Views: 6621
Reputation: 1492
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:
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);
Upvotes: 0
Reputation: 16096
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
}
Upvotes: 3