Dateien nach "NTLM" hochladen

This commit is contained in:
cpi
2026-04-22 13:38:28 +02:00
parent 7b31295c22
commit 575d73ebfb
3 changed files with 658 additions and 0 deletions
+345
View File
@@ -0,0 +1,345 @@
<#
.SYNOPSIS
Retrieves ticket and session key encryption types
.DESCRIPTION
Searches the Security Event Log for instances of Event Id 4769 and Event Id 4768 to create a list of encryption types used in Kerberos tickets
.EXAMPLE
Get-KerbEncryptionUsage # This will list all requests seen in the the past 30 days
.EXAMPLE
Get-KerbEncryptionUsage -Encryption RC4 -EncryptionUsage Ticket # This will list all requests that used RC4 in the Ticket encryption
.EXAMPLE
Get-KerbEncryptionUsage -Searchscope AllKdcs -Since (Get-Date).AddDays(-7) # This will list all requests querying all KDCs for events in the past 7 days
.PARAMETER Encryption
Specifies the encryption type to be queried
.PARAMETER Since
Specifies the earliest point to be queried from
.PARAMETER SearchScope
Specifies whether the query should be the local machine or all KDCs
.PARAMETER EncryptionUsage
Specifies where to check for encryption usage. Ticket, SessionKey, Either or Both
.NOTES
Author: Will Aftring (wiaftrin)
When specifying AllKdcs, to pull the event log results remote Event Log reading must be enabled.
Copyright (c) Microsoft Corporation. All rights reserved.
#>
[CmdletBinding()]
param(
[ValidateSet("RC4", "DES", "AES-SHA1", "AES128-SHA96", "AES256-SHA96", "All")]
[string]$Encryption = "All",
[DateTime]$Since = $(Get-Date).AddDays(-30),
[ValidateSet("This", "AllKdcs")]
[string]$SearchScope = "This",
[ValidateSet("Ticket", "SessionKey", "Either", "Both")]
[string]$EncryptionUsage = "Either"
)
#region Classes
class EncryptionType {
[string]$Name
[int]$Value
EncryptionType([string]$name, [int]$value) {
$this.Name = $name
$this.Value = $value
}
[string]ToDataString() {
return "Data='0x{0:x}'" -f $this.Value
}
[string]ToString() {
return $this.Name
}
[bool]Equals([object]$other) {
if ($null -eq $other -or $this.GetType() -ne $other.GetType()) {
return $false
}
$EType = [EncryptionType]$other
return $EType.Name -eq $this.Name -and $EType.Value -eq $this.Value
}
}
enum RequestType {
AS
TGS
}
class KerbRequest {
hidden [long]$RecordId
[string]$MachineName
[DateTime]$Time
[string]$Requestor
[string]$Source
[string]$Target
[RequestType]$Type
[EncryptionType]$Ticket
[EncryptionType]$SessionKey
KerbRequest([long]$id, [string]$m, [datetime]$tc, [string]$r, [string]$s, [string]$t, [RequestType]$rt, [EncryptionType]$te, [EncryptionType]$se) {
$this.RecordId = $id
$this.MachineName = $m
$this.Time = $tc
if ($r.StartsWith("::ffff:")) {
$r = $r.Replace("::ffff:", "")
}
$this.Requestor = $r
$this.Source = $s
$this.Target = $t
$this.Type = $rt
$this.Ticket = $te
$this.SessionKey = $se
}
[string] GetEvtFilter() {
return @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">*[System[(EventRecordID=$($this.RecordId))]]</Select>
</Query>
</QueryList>
"@
}
[System.Diagnostics.Eventing.Reader.EventLogRecord] ShowEvent() {
$query = $this.GetEvtFilter()
if ($this.MachineName.ToUpper() -eq "$ENV:COMPUTERNAME`.$ENV:USERDNSDOMAIN") {
return Get-WinEvent -FilterXPath $query -LogName Security
} else {
return Get-WinEvent -FilterXPath $query -LogName Security -ComputerName $this.MachineName
}
}
}
#endregion
#region Globals
$script:DES_CRC = [EncryptionType]::new("DES-CRC", 0x1)
$script:DES_MD5 = [EncryptionType]::new("DES-MD5", 0x3)
$script:RC4 = [EncryptionType]::new("RC4", 0x17)
$script:AES128 = [EncryptionType]::new("AES128-SHA96", 0x11)
$script:AES256 = [EncryptionType]::new("AES256-SHA96", 0x12)
$script:AES128_SHA256 = [EncryptionType]::new("AES128-SHA256", 0x13)
$script:AES256_SHA384 = [EncryptionType]::new("AES256-SHA384", 0x14)
$script:UnknownEType = [EncryptionType]::new("Unknown", 0xFF)
$script:EncryptionTypes = @(
$script:DES_CRC
$script:DES_MD5
$script:RC4
$script:AES128
$script:AES256
$script:AES128_SHA256
$script:AES256_SHA384
$script:UnknownEType
)
<#
The new properties counts are 21 for 4769 and 24 for 4668. Meaning if we have a lower
property count then we are reading the old event data.
#>
$script:MIN_PROPERTY_COUNT = 21
$script:XPathQuery = @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=4769) and (TimeCreated[@SystemTime >= '$($Since.ToString("yyyy-MM-ddTHH:mm:ss"))'])]]
</Select>
</Query>
<Query Id="1" Path="Security">
<Select Path="Security">
*[System[(EventID=4768) and (TimeCreated[@SystemTime >= '$($Since.ToString("yyyy-MM-ddTHH:mm:ss"))'])]]
</Select>
</Query>
</QueryList>
"@
#endregion
#region Functions
function Get-KdcEventLog {
param(
[string]$KDCName = $null,
[string]$Query
)
Write-Debug "Query:`n$Query to KDC '$KDCName'"
Write-Verbose "Attempting to query $KDCName"
$Results = $null
try {
if ([string]::IsNullOrEmpty($KDCName)) {
$Results = Get-WinEvent -FilterXPath $Query -LogName Security -ErrorAction Stop
}
else {
$Results = Get-WinEvent -ComputerName $KDCName -FilterXPath $Query -LogName Security -ErrorAction Stop
}
}
catch {
if ($_.FullyQualifiedErrorId -eq "NoMatchingEventsFound,Microsoft.PowerShell.Commands.GetWinEventCommand") {
$RealKdcName = if ($null -eq $KDCName) { "$ENV:COMPUTERNAME" } else { $KDCName }
Write-Warning "No events found on $RealKdcName"
}
else {
throw $_
}
}
Write-Debug "$Results"
return $Results
}
function Check-ETypeUsage {
param(
[string]$UsageMode,
[EncryptionType]$TicketEtype,
[EncryptionType]$SKEtype,
[EncryptionType]$SearchEtype
)
if ("Both" -eq $EncryptionUsage) {
return $($TicketEtype -eq $SKEtype -and $SearchEtype -eq $TicketEtype)
}
elseif ("Ticket" -eq $EncryptionUsage) {
return $($TicketEtype -eq $SearchEtype)
}
elseif ("SessionKey" -eq $EncryptionUsage) {
return $($SKEtype -eq $SearchEtype)
}
else {
return $($SKEtype -eq $SearchEtype -or $TicketEtype -eq $SearchEtype)
}
}
function Get-EncryptionType {
param(
[Parameter(Mandatory = $true, ParameterSetName = "Name")]
[string]$Name,
[Parameter(Mandatory = $true, ParameterSetName = "Value")]
[int]$Value
)
foreach ($etype in $script:EncryptionTypes) {
if (($PSCmdlet.ParameterSetName -eq "Name" -and $etype.Name -eq $Name) `
-or ($PSCmdlet.ParameterSetName -eq "Value" -and $etype.Value -eq $Value)) {
return $etype
}
}
return $script:UnknownEType
}
function Get-EncryptionTypes {
return $script:EncryptionTypes
}
function Get-KerbEncryptionUsage {
[CmdletBinding()]
param(
[ValidateSet("RC4", "DES", "AES-SHA1", "AES128-SHA96", "AES256-SHA96", "All")]
[string]$Encryption = "All",
[DateTime]$Since = $(Get-Date).AddDays(-30),
[ValidateSet("This", "AllKdcs")]
[string]$SearchScope = "This",
[ValidateSet("Ticket", "SessionKey", "Either", "Both")]
[string]$EncryptionUsage = "Either"
)
$Events = [System.Collections.ArrayList]::new()
if ("AllKdcs" -eq $SearchScope) {
Get-ADDomainController -Filter * | ForEach-Object {
$KDCName = $_.HostName
try {
[Array]$KdcResult = $(Get-KdcEventLog -KDCName $KDCName -Query $script:XPathQuery)
if ($null -ne $KdcResult -and 0 -ne $KdcResult.Count) {
$Events.AddRange($KdcResult)
}
}
catch {
Write-Error "Failed to get event logs from $KDCName with result: $_"
}
}
}
else {
try {
[Array]$LocalResult = $(Get-KdcEventLog -Query $script:XPathQuery)
if ($null -ne $LocalResult -and 0 -ne $LocalResult.Count) {
$Events.AddRange($LocalResult)
}
}
catch {
Write-Error "Failed to get event logs from $KDCName with result: $_"
}
}
# Validate we are working with the correct version
if ($accounts.Count -gt 0 -and $accounts[0].Properties.Count -lt $script:MIN_PROPERTY_COUNT) {
Write-Error "Attempting to run script on Windows Version $([System.Environment]::OSVersion.Version) which doesn't have the new event metadata.
Please install the most recent Windows Updates available for this machine and attempt again."
return
}
Write-Verbose "Total events: $($Events.Count)"
$Events | ForEach-Object {
$ShowRequest = $true
$T = $null
$SK = $null
$R = $null
$Target = $null
$IP = $null
if ($_.Id -eq 4769) {
$Target = $_.Properties[2].Value
$T = Get-EncryptionType -Value $_.Properties[5].Value
$SK = Get-EncryptionType -Value $_.Properties[20].Value
$R = [RequestType]::TGS
$IP = $_.Properties[6].Value
}
else {
$Target = $_.Properties[3].Value
$T = Get-EncryptionType -Value $_.Properties[7].Value
$SK = Get-EncryptionType -Value $_.Properties[22].Value
$R = [RequestType]::AS
$IP = $_.Properties[9].Value
}
if ("DES" -eq $Encryption) {
$D1 = Check-ETypeUsage -UsageMode $EncryptionUsage -TicketEtype $T -SKEtype $SK -SearchEtype $script:DES_CRC
$D2 = Check-ETypeUsage -UsageMode $EncryptionUsage -TicketEtype $T -SKEtype $SK -SearchEtype $script:DES_MD5
$ShowRequest = $D1 -or $D2
}
elseif ("AES-SHA1" -eq $Encryption) {
$A1 = Check-ETypeUsage -UsageMode $EncryptionUsage -TicketEtype $T -SKEtype $SK -SearchEtype $script:AES128
$A2 = Check-ETypeUsage -UsageMode $EncryptionUsage -TicketEtype $T -SKEtype $SK -SearchEtype $script:AES256
$ShowRequest = $A1 -or $A2
}
elseif ("All" -ne $Encryption) {
$Etype = Get-EncryptionType -Name $Encryption
$ShowRequest = $(Check-ETypeUsage -UsageMode $EncryptionUsage -TicketEtype $T -SKEtype $SK -SearchEtype $EType)
}
if ($ShowRequest) {
[KerbRequest]::new($_.RecordId, $_.MachineName, $_.TimeCreated, $IP, $_.Properties[0].Value, $Target, $R, $T, $SK)
}
}
}
#endregion
if ($MyInvocation.InvocationName -ne ".") {
Get-KerbEncryptionUsage @PSBoundParameters
}
#endregion
+268
View File
@@ -0,0 +1,268 @@
<#
.SYNOPSIS
Retrieves the observed Account Key types
.DESCRIPTION
Searches the Security Event Logs for intstances of Event Id 4769 and Event Id 4768 to determine which account keys are used.
.EXAMPLE
List-AccountKeys # This will list all accounts and their key types found in the past 90 days
.PARAMETER Since
Specifies the earliest time to be searched since
.PARAMETER SearchScope
Specifies whether we should search all KDCs in the domain or just the local machine
.NOTES
Author: Will Aftring (wiaftrin)
When specifying AllKdcs, to pull the event log results remote Event Log reading must be enabled.
Copyright (c) Microsoft Corporation. All rights reserved.
#>
[CmdletBinding()]
param(
[DateTime]$Since = $(Get-Date).AddDays(-30),
[ValidateSet("DES", "RC4", "AES-SHA1", "All")]
[string]$ContainsKeyType = "All",
[ValidateSet("DES", "RC4", "AES-SHA1", "None")]
[string]$NotContainsKeyType = "None",
[ValidateSet("This", "AllKdcs")]
[string]$SearchScope = "This"
)
<#
N.B(wiaftrin): On Windows Server 2022 the AES-SHA1 keys are aggregated into a single string.
On Windows Server 2025+, the keys are called out individually.
#>
# AES-SHA1 on 2022-
$script:AES_SHA1_FILTER_2022 = "AES-SHA1"
#AES-SHA1 on 2025+
$script:AES_SHA1_FILTER_2025 = "SHA96"
enum AccountType {
User
Machine
Service
}
class Account {
hidden [long]$RecordId
[string]$MachineName
[datetime]$Time
[string]$Name
[AccountType]$Type
[string]$Keys
Account([long]$id, [string]$m, [datetime]$tc, [string]$name, [AccountType]$ct, [string]$ckeys) {
$this.RecordId = $id
$this.MachineName = $m
$this.Time = $tc
$this.Name = $name
$this.Type = $ct
$tmp = [System.Collections.ArrayList]::new()
$ckeys.Split(",").Trim() | ForEach-Object {
if ($_ -eq $script:AES_SHA1_FILTER_2022) {
$tmp.Add("AES128-SHA96")
$tmp.Add("AES256-SHA96")
}
else {
$tmp.Add($_)
}
}
$this.Keys = $tmp -join '; ' # Using ; for CSV to be more CSV friendly
}
[string] GetEvtFilter() {
return @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">*[System[(EventRecordID=$($this.RecordId))]]</Select>
</Query>
</QueryList>
"@
}
[System.Diagnostics.Eventing.Reader.EventLogRecord] ShowEvent() {
$query = $this.GetEvtFilter()
if ($this.MachineName.ToUpper() -eq "$ENV:COMPUTERNAME`.$ENV:USERDNSDOMAIN") {
return Get-WinEvent -FilterXPath $query -LogName Security
} else {
return Get-WinEvent -FilterXPath $query -LogName Security -ComputerName $this.MachineName
}
}
}
#endregion
#region Globals
$script:XPathQuery = @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=4769) and (TimeCreated[@SystemTime >= '$($Since.ToString("yyyy-MM-ddTHH:mm:ss"))'])]]
</Select>
</Query>
<Query Id="1" Path="Security">
<Select Path="Security">
*[System[(EventID=4768) and (TimeCreated[@SystemTime >= '$($Since.ToString("yyyy-MM-ddTHH:mm:ss"))'])]]
</Select>
</Query>
</QueryList>
"@
<#
The new properties counts are 21 for 4769 and 24 for 4668. Meaning if we have a lower
property count then we are reading the old event data.
#>
$script:MIN_PROPERTY_COUNT = 21
$script:KeyFilter = ""
$script:NotKeyFilter = ""
#endregion
#region Functions
function Get-AccountsFromKDC {
param(
[string]$KDCName = $null,
[string]$Query
)
Write-Debug "Query:`n$Query to KDC '$KDCName'"
Write-Verbose "Attempting to query $KDCName"
$Results = $null
try {
if ([string]::IsNullOrEmpty($KDCName)) {
$Results = Get-WinEvent -FilterXPath $Query -LogName Security -ErrorAction Stop
}
else {
$Results = Get-WinEvent -ComputerName $KDCName -FilterXPath $Query -LogName Security -ErrorAction Stop
}
}
catch {
if ($_.FullyQualifiedErrorId -eq "NoMatchingEventsFound,Microsoft.PowerShell.Commands.GetWinEventCommand") {
$RealKdcName = if ($null -eq $KDCName) { "$ENV:COMPUTERNAME" } else { $KDCName }
Write-Warning "No events found on $RealKdcName"
}
else {
throw $_
}
}
return $Results
}
function List-AccountKeys {
[CmdletBinding()]
param(
[DateTime]$Since = $(Get-Date).AddDays(-30),
[ValidateSet("DES", "RC4", "AES-SHA1", "All")]
[string]$ContainsKeyType = "All",
[ValidateSet("DES", "RC4", "AES-SHA1", "None")]
[string]$NotContainsKeyType = "None",
[ValidateSet("This", "AllKdcs")]
[string]$SearchScope = "This"
)
if ("All" -ne $ContainsKeyType) {
# translate AES-SHA1 into either
if ("AES-SHA1" -eq $ContainsKeyType) {
$script:KeyFilter = $script:AES_SHA1_FILTER
}
elseif ("DES" -eq $ContainsKeyType) {
$script:KeyFilter = "DES"
}
else {
$script:KeyFilter = $ContainsKeyType
}
}
if ("None" -ne $NotContainsKeyType) {
if ("AES-SHA1" -eq $NotContainsKeyType) {
$script:NotKeyFilter = $script:AES_SHA1_FILTER
}
elseif ("DES" -eq $ContainsKeyType) {
$script:NotKeyFilter = "DES"
}
else {
$script:NotKeyFilter = $NotContainsKeyType
}
}
$accounts = [System.Collections.ArrayList]::new()
if ("This" -eq $SearchScope) {
[Array]$LocalResult = $(Get-AccountsFromKDC -Query $script:XPathQuery)
if ($null -ne $LocalResult -and 0 -ne $LocalResult.Count) {
$accounts.AddRange($LocalResult)
}
}
else {
Get-ADDomainController -Filter * | ForEach-Object {
$KDCName = $_.HostName
try {
[Array]$KdcResult = $(Get-AccountsFromKDC -KDCName $KDCName -Query $script:XPathQuery)
if ($null -ne $KdcResult -and 0 -ne $KdcResult.Count) {
$accounts.AddRange($KdcResult)
}
}
catch {
Write-Error "Failed to get event logs from $KDCName with result: $_"
}
}
}
# Validate we are working with the correct version
if ($accounts.Count -gt 0 -and $accounts[0].Properties.Count -lt $script:MIN_PROPERTY_COUNT) {
Write-Error "Attempting to run script on Windows Version $([System.Environment]::OSVersion.Version) which doesn't have the new event metadata.
Please install the most recent Windows Updates available for this machine and attempt again."
return
}
Write-Verbose "Accounts returned: $($accounts.Count)"
$accounts | ForEach-Object {
$KDC = $_.MachineName
[string]$keys = $_.Properties[16].Value
if (-not [string]::IsNullOrEmpty($script:NotKeyFilter)) {
if ($keys.Contains($script:NotKeyFilter)) {
continue
}
}
if (-not [string]::IsNullOrEmpty($script:KeyFilter)) {
if (-not $keys.Contains($script:KeyFilter)) {
continue
}
}
if (4769 -eq $_.Id) {
$type = [AccountType]::Service
$target = $_.Properties[2].Value
if ($target.EndsWith("$")) {
$type = [AccountType]::Machine
}
[Account]::new($_.RecordId, $KDC, $_.TimeCreated, $target, $type, $keys)
}
else {
$target = $_.Properties[0].Value
$type = if ($target.EndsWith("$")) {[AccountType]::Machine } else { [AccountType]::User }
[Account]::new($_.RecordId, $KDC, $_.TimeCreated, $target, $type, $keys)
}
}
}
#endregion
if ($MyInvocation.InvocationName -ne ".") {
List-AccountKeys @PSBoundParameters
}
+45
View File
@@ -0,0 +1,45 @@
# Set the number of days to check for unchanged passwords
$daysThreshold = 1000
# Prompt the user to select an Organizational Unit (OU)
$selectedOU = Read-Host "Enter the DistinguishedName of an OU (EX: OU=SUBNAME,OU=Name,DC=DOMAIN,DC=LOCAL). Leave blank to search the entire domain."
# Get the current date
$currentDate = Get-Date
# Calculate the date X days ago
$thresholdDate = $currentDate.AddDays(-$daysThreshold)
# Get users from Active Directory
if ([string]::IsNullOrEmpty($selectedOU)) {
$users = Get-ADUser -Filter * -Properties PasswordLastSet, Enabled, DisplayName, CannotChangePassword
} else {
$users = Get-ADUser -Filter * -SearchBase $selectedOU -Properties PasswordLastSet, Enabled, DisplayName, CannotChangePassword
}
# Iterate through each user and check if their password has not been changed in X days
$results = @()
foreach ($user in $users) {
if ($user.Enabled -eq $true) {
$passwordLastSet = $user.PasswordLastSet
# If PasswordLastSet is null or empty, skip the user
if (-not $passwordLastSet) {
continue
}
# Compare the last password change date with the threshold date
if ($passwordLastSet -lt $thresholdDate) {
$result = [PSCustomObject]@{
DisplayName = $user.DisplayName
SAMAccountName = $user.SamAccountName
LastPasswordSet = $passwordLastSet
CanChangePassword = -not $user.CannotChangePassword
}
$results += $result
}
}
}
# Output the results in a table format
$results | Sort LastPasswordSet | Format-Table -AutoSize