Dateien nach "NTLM" hochladen
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user