Skip to content

Instantly share code, notes, and snippets.

@JoeDibley
Last active May 25, 2023 18:48
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoeDibley/fd93a9c5b3d45dbd8cbfdd003ddc1bd1 to your computer and use it in GitHub Desktop.
Save JoeDibley/fd93a9c5b3d45dbd8cbfdd003ddc1bd1 to your computer and use it in GitHub Desktop.
Checks the username of logged in users to the Kerberos Tickets. Whilst not the best it is a potential local based detection for things like Pass-the-Ticket, Golden Ticket type attacks
<#
This is comprised of 3 functions:
1. Get-Sessions: Get Session information from WMI.
Reused some code from https://github.com/tmmtsmith/Powershell/blob/master/Get-LoggedOnUsers.ps1 for the regex matching
2. Return-SessionTickets: Retrieves Kerberos Session Tickets. This is a klist wrapper to return PSCustomObjects
This code is mostly all from the GetKerbTix.ps1 script in the Technet Script Center
https://gallery.technet.microsoft.com/scriptcenter/List-All-Cached-Kerberos-5ba41829
3. Invoke-PtTCheck: Perform username matching to see if the username logged on has different username to Kerberos tickets registered for their session
This is not by any means a full detection for Pass-the-Ticket or Golden Ticket but can just show 1 simple way
in which a blue team could go about checking for something like this on a compromised client.
USAGE
1. Dot source the script
PS> . Invoke-PtTCheck.ps1
2. Run Invoke-PtTCheck
#>
function Get-Sessions
{
param
(
[parameter(Mandatory = $false)]
[String[]]
$computerName = "localhost"
)
Write-Verbose -Message "Executing Win32_LoggedOnUser on $Computer"
$logon_users = Get-WmiObject win32_loggedonuser -ComputerName $ComputerName
if ($logon_users)
{
Write-Verbose -Message "Found $($logon_users.count) Logon Sessions on $Computer"
foreach ($Logon_User in $Logon_Users)
{
# Ensure Antecedent Matches the Domain and Name format expected. PowerShell Automatically stores matches in $Matches variable.
$Logon_User.antecedent -match $regexa | Out-Null
#Create Domain\Username format
$username = $matches[1] + "\" + $matches[2]
# Ensure Dependent matches the expected logonID format
$Logon_User.dependent -match $regexd | Out-Null
# Extract Logon ID from matches
$session = $matches[1]
# Put Logon ID in the Session Hex Format which is used by klist
$sessionHex = ('0x{0:X}' -f [int]$session)
# Create and output objects
$Object = New-Object -TypeName psobject
$Object | Add-Member -MemberType NoteProperty -Name "Session" -Value $sessionHex
$Object | Add-Member -MemberType NoteProperty -Name "Username" -Value $username
$Object
}
}
}
function Return-SessionTickets
{
param
(
[parameter(Mandatory = $false)]
$SessionID = $null,
[Switch]$TimeOutput
)
$OS = Get-WmiObject win32_operatingsystem
if ($SessionID -eq $null)
{
$TicketsArray = klist.exe tickets
}
else
{
$TicketsArray = klist.exe tickets -li $sessionID
if ($TicketsArray -like "*Error calling API LsaCallAuthenticationPackage*")
{
$SessionID = "0:$sessionID"
$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)
# Convert Start and End Times to actual dates
[datetime]$StartTime_Obj = $StartTime.TrimEnd('(local)')
[datetime]$EndTime_Obj = $EndTime.TrimEnd('(local)')
[datetime]$RenewTime_Obj = $RenewTime.TrimEnd('(local)')
# Calculate the Service Ticket Lifetime
$Ticket_Lifetime = New-TimeSpan -Start $StartTime_Obj -End $EndTime_Obj
$Renew_Lifetime = New-TimeSpan -Start $StartTime_Obj -End $RenewTime_Obj
# Get Service From SPN
$Service = $Server.split('/')[0]
Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "TicketNumber" -Value $TicketNumber
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 "KerbTicketEncryptionType" -Value $KerbTicketEType
Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "TicketFlags" -Value $TickFlags
Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "StartTime" -Value $StartTime_Obj
Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "EndTime" -Value $EndTime_Obj
Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "RenewTime" -Value $RenewTime_Obj
Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "SessionKeyType" -Value $SessionKey
Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "TicketLifetime" -Value $Ticket_Lifetime
Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "Service" -Value $Service
Add-Member -InputObject $Ticket -MemberType NoteProperty -Name "RenewLifetime" -Value $Renew_Lifetime
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
}
[array]$Tickets += $Ticket
}
$Counter++
}
If ($TimeOutput)
{
return $Tickets | select-object Client, Service, StartTime, EndTime, RenewTime, Ticketlifetime, RenewLifetime | Sort-Object -Descending TicketLifetime | Format-Table
}
else
{
return $Tickets
}
}
function Invoke-PtTCheck
{
param
(
[parameter(Mandatory = $false)]
[string[]]$ComputerName = "localhost" # For when / if it can support multiple hosts
)
BEGIN
{
# Regex Matches to get session info
$regexa = '.+Domain="(.+)",Name="(.+)"$'
$regexd = '.+LogonId="(\d+)"$'
}
PROCESS
{
foreach ($Computer in $ComputerName)
{
[pscustomobject]$sessions = Get-Sessions -computerName $Computer
foreach ($Session in $sessions)
{
# Use Wrapper Function for Klist to search for tickets for the use
$Session_Tickets = Return-SessionTickets -sessionID $Session.Session
if ($Session_Tickets)
{
# Get Unique Client (Username) from Ticket FORMAT = Username @ FQDN Domain
$Unique_Username_for_session = $Session_Tickets.Client | Select-Object -Unique
$Unique_Username_for_session_Formatted = $Unique_Username_for_session.split(" ")[0]
Write-Verbose -Message "Unique Username in $sessionHex`: $Unique_Username_for_session_Formatted"
# Due to weird formatting and potential for different netbios vs full dns name
# of domain just check to see if the username in the session ticket is on the right of the username field
if ($Session.username.split('\')[-1] -eq $Unique_Username_for_session_Formatted)
{
$Session | Add-Member -MemberType NoteProperty -Name "IsPtT" -Value $False
$Session | Add-Member -MemberType NoteProperty -Name "PtTUser" -Value $null
}
elseif ($Session.Username.split('\')[0] -eq $Unique_Username_for_session_Formatted.trimend('$'))
{
# This is to match computer name in the domain field to the computername in Kerberos tickets for SYSTEM / Local Service / Network Service Services
$Session | Add-Member -MemberType NoteProperty -Name "IsPtT" -Value $False
$Session | Add-Member -MemberType NoteProperty -Name "PtTUser" -Value $null
}
else
{
$Session | Add-Member -MemberType NoteProperty -Name "IsPtT" -Value $true
$Session | Add-Member -MemberType NoteProperty -Name "PtTUser" -Value $Unique_Username_for_session_Formatted
}
}
else
{
Write-Verbose -Message "No session tickets for user: $($Session.Username)"
$Session | Add-Member -MemberType NoteProperty -Name "IsPtT" -Value $false
$Session | Add-Member -MemberType NoteProperty -Name "PtTUser" -Value $null
}
[array] $sessionOutput += $Session
}
}
}
END
{
$sessionOutput
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment