我知道我可以通过运行“klist.exe”并解析输出来获得所需的信息,但我想知道是否有 Windows/C#/Powershell API 来获取有关 Windows 服务器上缓存的 Kerberos 票证的信息。
2 回答
Microsoft 已经为此提供了一组脚本。所以,你不必从头开始写这个。查看和清除缓存的 Kerberos 票证,是的,他们有 klist。否则,你最终会试图利用……</p>
[System.Security.Principal.WindowsIdentity]
…然后进行 SID 翻译等,否则您最终会在此问答中进行相同的讨论。
或者利用这些资源并根据需要进行调整。
Kerberos 模块 该模块提供对 Kerberos 票证缓存的访问权限。它可以读取和清除当前登录会话的票证。
Kerberos.NET 的重点是使 Kerberos 在这种情况下更易于使用。这是通过删除对 Windows 的任何硬依赖并将所有票证处理转移到应用程序本身来完成的。这当然意味着您不需要应用程序位于已加入域的计算机上,而且它可能也不需要位于 Windows 上。
Install-Package Kerberos.NET
使用库
票证身份验证分两个阶段进行。第一阶段通过具有 KerberosValidator 的默认实现的 IKerberosValidator 验证票证的正确性。第二阶段涉及将票证转换为可用的 ClaimsIdentity,这发生在 KerberosAuthenticator 中。
最简单的开始方法是创建一个新的 KerberosAuthenticator 并调用 Authenticate。如果您需要调整转换的行为,可以通过重写 ConvertTicket(DecryptedData data) 方法来实现。
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));
请注意,身份验证器的构造函数参数是 KeyTable。KeyTable 是用于在其他平台上存储密钥的常用格式。您可以使用由 ktpass 之类的工具创建的文件,也可以在实例化期间只传递一个 KerberosKey,它会产生相同的效果。
列出所有缓存的 Kerberos 票证
在域中对身份验证进行管理或故障排除时,有时您需要知道用户和服务的票证是否缓存在计算机上。此脚本将计算机上所有用户缓存的票证导出到文本文件以供查看。
下载: GetKerbTix.ps1
清除所有 Kerberos 票证
在某些情况下,管理员可能想要清除服务器上缓存的 Kerberos 票证。例如,用户 Bob 离开了公司。在这种情况下,您可以运行此脚本来清除计算机上所有会话的所有缓存 Kerberos 票证和 TGT。
#************************************************
# 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
}
到目前为止,我能够找到 klist.exe 的源代码,并且“LsaCallAuthenticationPackage”似乎是在 Windows 中与 Kerberos 缓存进行通信的方式:
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);