This script goes out and pulls every Active Directory User object with ServicePrincipalName’s (SPN) defined and gathers important information about that account.
Here are things the script looks for:
- Is the SPN’s computer that is defined currently online.
- Is the User Account set up with Kerberos Constrained vs UnConstrained Delegation.
- What kind of encryption is being used for the accounts password. (msds-supportedencryptiontypes)
- Checks to see if the Account is a member of a Domain Privileged Group.
- Also checks so see if the primary Group Membership has changed.
- The password age.
Our Recommendations:
- No Accounts should be set up with Unconstrained Kerberos Delegation
- None of these accounts should be in a privileged group. Example – Domain Admins or Enterprise Admins
- Passwords should be changes with a a minimum 15, preferred 25 character password.
- Only Valid SPN’s should be defined.
- DES Encryption should not be used.
Here is an example of the output
This file contains 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" |
Here is the source code which can be found on Github: Link
And that is all for now.
-Chad
from TechNet Blogs https://ift.tt/2Fh6i71
No comments:
Post a Comment