Last active
August 29, 2024 16:27
-
-
Save chadmcox/0fe781f324034f03a7fc6bdd7a4fe471 to your computer and use it in GitHub Desktop.
This powershell script will assist in finding accounts that would make great targets for Kerberoasting. This script will also assist in validating spn's defined on user objects.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #Requires -Module activedirectory | |
| #Requires -version 4.0 | |
| <#PSScriptInfo | |
| .VERSION 1.5 | |
| .GUID 31adb560-b189-4b0c-86a7-7862d8e78094 | |
| .AUTHOR Chad.Cox@microsoft.com | |
| https://blogs.technet.microsoft.com/chadcox/ | |
| https://github.com/chadmcox | |
| .COPYRIGHT This Sample Code is provided for the purpose of illustration only and is not | |
| intended to be used in a production environment. THIS SAMPLE CODE AND ANY | |
| RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER | |
| EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF | |
| MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a | |
| nonexclusive, royalty-free right to use and modify the Sample Code and to | |
| reproduce and distribute the object code form of the Sample Code, provided | |
| that You agree: (i) to not use Our name, logo, or trademarks to market Your | |
| software product in which the Sample Code is embedded; (ii) to include a valid | |
| copyright notice on Your software product in which the Sample Code is embedded; | |
| and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and | |
| against any claims or lawsuits, including attorneys` fees, that arise or result | |
| from the use or distribution of the Sample Code.. | |
| .RELEASENOTES | |
| 1.6 Coming soon - Will thread out the computer validation. as-is it takes for ever | |
| in large environments. | |
| 1.5 update to display if constrained delegation is using protocol transitioning | |
| 1.4 Script clean up, and enhancements. | |
| .EXAMPLE | |
| .\findSneakyAccounts.ps1 | |
| Returns all user accounts that have spn's defined | |
| .EXAMPLE | |
| .\findSneakyAccounts.ps1 -OnlySensitiveAccounts | |
| Returns Only Senstive Accounts that have defined SPNs | |
| .EXAMPLE | |
| .\findSneakyAccounts.ps1 -OnlySensitiveAccounts | |
| Returns Only Senstive Accounts that have defined SPNs | |
| .EXAMPLE | |
| .\findSneakyAccounts.ps1 -offlinespnreport | |
| Includes a report to show which SPN's are not showing online. | |
| .DESCRIPTION | |
| Article Associated to this script. | |
| https://blogs.technet.microsoft.com/chadcox/2018/04/25/active-directory-powershell-script-to-report-on-users-with-spns/ | |
| Articles and Script Ideas used | |
| #https://adsecurity.org/?p=3466 | |
| #https://raw.githubusercontent.com/cyberark/RiskySPN/master/Find-PotentiallyCrackableAccounts.ps1 | |
| This script could be used to assist in finding possible kerberoasting accounts. | |
| It could also be used to assist in finding possible stale accounts | |
| This does require the ActiveDirectory module and at least version 4.0 powershell installed. | |
| To install the activedirectory module | |
| Add-WindowsFeature RSAT-AD-PowerShell | |
| #> | |
| Param([switch]$offlineSPNReport, | |
| [switch]$OnlySensitiveAccounts, | |
| $reportpath = "$env:userprofile\Documents") | |
| cls | |
| $serviceAccounts = @() | |
| $results = @() | |
| $Default_Group_ID = 513 | |
| Function collectADUserswithSPN{ | |
| $results = @() | |
| Write-host "Gathering List of AD Users" | |
| $userProperties = @("whencreated","lastlogontimestamp","SamAccountName",` | |
| "UserAccountControl","Enabled","admincount","Trustedfordelegation",` | |
| "TrustedToAuthForDelegation","PrimaryGroupID","pwdlastset","sidhistory","mail", ` | |
| "PasswordNotRequired","distinguishedname","UserPrincipalname","PasswordExpired","LockedOut", ` | |
| "ProtectedFromAccidentalDeletion","servicePrincipalname","msds-supportedencryptiontypes", ` | |
| "msds-allowedtodelegateto") | |
| $select_properties = $userProperties + $hash_domain | |
| foreach($domain in (get-adforest).domains){ | |
| get-aduser -ldapfilter "(&(servicePrincipalName=*)(!(samaccountname=KRBTGT)))" ` | |
| -Properties $userProperties -server $domain | select $select_properties | |
| } | |
| } | |
| function validateprivgroupmembership{ | |
| param($object) | |
| $result = $false | |
| $odn = $object.distinguishedname | |
| $default_admin_groups = foreach($domain in (get-adforest).domains){get-adgroup ` | |
| -filter {admincount -eq 1 -and iscriticalsystemobject -like "*"} ` | |
| -server $domain | select $hash_domain,distinguishedname} | |
| foreach($group in $default_admin_groups){ | |
| if(Get-ADgroup -Filter {member -RecursiveMatch $odn} -searchbase $group.distinguishedname ` | |
| -server $group.domain){$result = $True} | |
| } | |
| $result | |
| } | |
| Function validatespnconnection{ | |
| param($object) | |
| $results = @() | |
| $spn = @() | |
| [array]$SPNs = $object.serviceprincipalname -replace ":.*" | Get-Unique | |
| foreach($spn in $spns){ | |
| $spn = $SPN -split("/") | |
| $objtmp = new-object -type psobject | |
| $objtmp | Add-Member -MemberType NoteProperty -Name "Account" -Value $object.samaccountname | |
| $objtmp | Add-Member -MemberType NoteProperty -Name "Computer" -Value $($spn[1]) | |
| $objtmp | Add-Member -MemberType NoteProperty -Name "Online" ` | |
| -Value $(if(Test-Connection -ComputerName $($spn[1]) -Quiet -Count 1){"Ping"} | |
| elseif(Test-netConnection -ComputerName $($spn[1]) -commontcpport RDP ` | |
| -informationlevel Quiet -WarningAction SilentlyContinue){"RDP"} | |
| elseif(Test-netConnection -ComputerName $($spn[1]) -commontcpport SMB ` | |
| -informationlevel Quiet -WarningAction SilentlyContinue){"SMB"} | |
| elseif(Test-netConnection -ComputerName $($spn[1]) -commontcpport WINRM ` | |
| -informationlevel Quiet -WarningAction SilentlyContinue){"WINRM"} | |
| elseif(Test-netConnection -ComputerName $($spn[1]) -commontcpport HTTP ` | |
| -informationlevel Quiet -WarningAction SilentlyContinue){"HTTP"} | |
| elseif(Test-netConnection -ComputerName $($spn[1]) -port 1433 ` | |
| -informationlevel Quiet -WarningAction SilentlyContinue){"SQL"} | |
| Else{$false}) | |
| $results += $objtmp | |
| #$objtmp | out-host | |
| } | |
| if($OfflineSPNReport){ | |
| $results | export-csv "$reportpath\reportPossibleUnusedSPNs.csv" -NoTypeInformation | |
| } | |
| "$(($results | where Online -eq $false | measure-object).count)" | |
| } | |
| #region hash | |
| #I use multiple hashes to create reusable calculated properties. | |
| $hash_domain = @{name='Domain';expression={$domain}} | |
| $hash_EncryptionType = @{name='EncryptionType'; | |
| expression={if($_.useraccountcontrol -band 2097152){"DES"} | |
| else{if($_."msds-supportedencryptiontypes" -band 16){"AES256-HMAC"} | |
| elseif($_."msds-supportedencryptiontypes" -band 8){"AES128-HMAC"} | |
| else{"RC4-HMAC"}}}} | |
| $hash_PasswordNeverExpires = @{Name="PasswordNeverExpires"; | |
| Expression={if($_.UserAccountControl -band 65536){$True}else{$False}}} | |
| $hash_UseDesKeyOnly = @{Name="UseDesKeyOnly";Expression={if($_.UserAccountControl -band 2097152){$True}else{$False}}} | |
| $hash_PrimaryGroup = @{Name="DefaultPrimaryGroup"; | |
| Expression={if($_.PrimaryGroupID -eq $Default_Group_ID){$True}else{$_.PrimaryGroupID}}} | |
| $hash_privgroupmembership = @{name='PrivilegedGroupMember';expression={validateprivgroupmembership -object $_}} | |
| $hash_spnvalidate = @{name='SPNEntriesOffline';expression={validatespnconnection -object $_}} | |
| $hash_spnentriescount = @{name='SPNEntriesCount'; | |
| expression={($_.serviceprincipalname -replace ":.*" | Get-Unique | measure-object).count}} | |
| $hash_kerbdelegationtype = @{name='DelegationType'; | |
| expression={if($_.TrustedToAuthForDelegation){"Protocol Transition"}Elseif($_.Trustedfordelegation){"UnConstrained"} | |
| elseif($_."msDS-AllowedToDelegateTo" -like "*" -and $_.TrustedToAuthForDelegation -ne $true){"Constrained"}else{"NA"}}} | |
| $hash_pwdLastSet = @{Name="pwdLastSet"; | |
| Expression={if($_.PwdLastSet -ne 0){([datetime]::FromFileTime($_.pwdLastSet).ToString('MM/dd/yyyy'))}}} | |
| $hash_whencreated = @{Name="whencreated"; | |
| Expression={($_.whencreated).ToString('MM/dd/yyyy')}} | |
| $hash_lastLogonTimestamp = @{Name="LastLogonTimeStamp"; | |
| Expression={if($_.LastLogonTimeStamp -like "*"){([datetime]::FromFileTime($_.LastLogonTimeStamp).ToString('MM/dd/yyyy'))}}} | |
| $hash_PwdAgeinDays = @{Name="PwdAgeinDays"; | |
| Expression={if($_.PwdLastSet -ne 0){(new-TimeSpan([datetime]::FromFileTimeUTC($_.PwdLastSet)) $(Get-Date)).days}else{"NA"}}} | |
| $hash_AllowtoDelegate = @{Name="AllowtoDelegateSet";Expression={if($_."ms-DS-Allowed-To-Delegate-To"){$True}else{$False}}} | |
| #endregion | |
| $accountswithspn = collectADUserswithSPN | select domain,samaccountname,enabled,PasswordExpired,LockedOut, ` | |
| $hash_EncryptionType,$hash_PasswordNeverExpires,$hash_UseDesKeyOnly,$hash_PrimaryGroup,$hash_privgroupmembership, ` | |
| $hash_kerbdelegationtype,$hash_AllowtoDelegate,TrustedToAuthForDelegation,Trustedfordelegation, ` | |
| $hash_spnvalidate,$hash_spnentriescount,$hash_PwdAgeinDays,$hash_pwdLastSet,$hash_lastLogonTimestamp,$hash_whencreated | |
| if($OnlySensitive){ | |
| $accountswithspn | where {$_.PrivilegedGroupMember -eq $true -and ($_.TrustedToAuthForDelegation -eq $true ` | |
| -or $_.Trustedfordelegation -eq $true)} | |
| $accountswithspn | where {$_.PrivilegedGroupMember -eq $true -and ($_.TrustedToAuthForDelegation -eq $true ` | |
| -or $_.Trustedfordelegation -eq $true)} | ` | |
| export-csv "$reportpath\reportOnlySensativeSneakyAccounts.csv" -NoTypeInformation | |
| }else{ | |
| $accountswithspn | |
| $accountswithspn | export-csv "$reportpath\reportAllAccountswithSPNs.csv" -NoTypeInformation | |
| } | |
| write-host "Reports can be found here: $reportpath" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment