-
Notifications
You must be signed in to change notification settings - Fork 72
/
Find-PotentiallyCrackableAccounts.ps1
360 lines (331 loc) · 16.7 KB
/
Find-PotentiallyCrackableAccounts.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
<#
Author: Matan Hart (@machosec)
License: GNU v3
Required Dependencies: None
Optional Dependencies: None
#>
function Find-PotentiallyCrackableAccounts
{
<#
.SYNOPSIS
Reveals juicy information about user accounts associated with SPN
Author: Matan Hart (@machosec)
License: GNU v3
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
This function queries the Active Directory and retreive information about user accounts associated with SPN.
This infromation could detremine if a service account is potentially crackable.
User accounts associated with SPN are vulnerable to offline brute-forceing and they are often (by defualt)
configured with weak password and encryption (RC4-HMAC).
Requires Active Directory authentication (domain user is enough).
.PARAMETER Domain
The name of the domain to query. "Current" for the user's current domain. Defualts to the entire forest.
.PARAMETER AddGroups
Add additional groups to consider as sensitive
.PARAMETER Sensitive
Show only sensitive accounts.
.PARAMETER Stealth
Do not check service/server connectivity
.PARAMETER GetSPNs
Show SPNs instead of user's data
.PARAMETER FullData
Show more user data
.EXAMPLE
Get-PotentiallyCrackableAccounts -Domain "IT.company.com"
Returns all user accounts associated with SPN in the IT.company.com domain.
.EXAMPLE
Get-PotentiallyCrackableAccounts -FullData -Verbose
Returns detailed information about all user accounts associated with SPN in the forest. Enable verbose mode
.EXAMPLE
Get-PotentiallyCrackableAccounts -AddGroups "Remote Desktop Users" -Sensitive -Stealth -GetSPNs
Returns all SPNs of sensitive user account in the forest. Consider "Remote Desktop Users" group as sensitive and do not check connectivity.
#>
[CmdletBinding()]
param
(
[string]$Domain,
[array]$AddGroups,
[switch]$Sensitive,
[switch]$Stealth,
[switch]$GetSPNs,
[switch]$FullData
)
#recursivly get nested groups of a group object
function Get-NestedGroups
{
[CmdletBinding()]
param
(
[parameter(Mandatory=$True, ValueFromPipeline=$True)]
[ValidateNotNullOrEmpty()]
[string]$DN
)
$GroubObj = [adsi]"LDAP://$DN"
#if the object is a group
if ($GroubObj.Properties.samaccounttype -match '536870912' -or $GroubObj.Properties.samaccounttype -match '268435456')
{
#Searching for group objects that are member of the group
foreach ($Member in $GroubObj.Properties.member)
{
#get group objects inside this group object
Get-NestedGroups -DN $Member
}
return $GroubObj.Properties.distinguishedname
}
}
#========================================================================= Creating ADSI Searcher =========================================================================
$SearchList = @()
if($Domain)
{
if ($Domain -eq "Current") {
$SearchScope = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
}
else
{
try {
$TargetDomain = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
$SearchScope = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($TargetDomain)
}
catch {
Write-Error "Could not communicate with the foreigen domain: $Domain"
return
}
}
if ($SearchScope.DomainMode.value__ -lt 4 -and $ChildDomain.DomainMode.value__ -ne -1) {
Write-Warning "The function level of domain: $($SearchScope.Name) is lower than 2008 - Some stuff may not work"
}
$SearchList += 'LDAP://DC=' + ($SearchScope.Name -Replace ("\.",',DC='))
Write-Verbose "Searching the domain: $($SearchScope.name)"
}
else
{
$SearchScope = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
foreach ($ChildDomain in $($SearchScope.Domains)) {
if ($ChildDomain.DomainMode.value__ -lt 4 -and $ChildDomain.DomainMode.value__ -ne -1) {
Write-Warning "The function level of domain: $($ChildDomain.Name) is lower than 2008 - Some stuff may not work"
}
$SearchList += 'LDAP://DC=' + ($ChildDomain.Name -Replace ("\.",',DC='))
}
Write-Verbose "Searching the forest: $($SearchScope.name)"
}
#creating ADSI searcher
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 500
$Searcher.PropertiesToLoad.Add("distinguishedname") | Out-Null
#========================================================================= Gathering Sensitive Groups =========================================================================
#list of built-in sensitive groups (Administratos group conatins domain and enterprise admins) - did I missed a group?
$SensitiveGroups = @("Administrators", "Account Operators", "Backup Operators", "Print Operators", "Server Operators", "Group Policy Creator Owners", "Schema Admins")
if ($AddGroups) {
Write-Verbose "Adding $AddGroups to the list of senstivie groups"
$SensitiveGroups += $AddGroups
}
$AllSensitiveGroups = @()
Write-Verbose "Gathering sensitive groups"
foreach ($Path in $SearchList) {
Write-Verbose "Searching Sensitive groups in domain: $($Path -replace "LDAP://DC=" -replace ",DC=", ".")"
$Searcher.SearchRoot = $Path
foreach ($GroupName in $SensitiveGroups) {
#filter group objects with specific name
$Searcher.Filter = "(&(|(samAccountType=536870912)(samAccountType=268435456))(|(samAccountName=$GroupName)(name=$GroupName)))"
try {
$GroupObjects = $Searcher.FindAll()
}
catch {
Write-Warning "Could not communicate with the domain: $($Path -replace "LDAP://DC=" -replace ",DC=", ".")"
}
if ($GroupObjects)
{
foreach ($GroupObject in $GroupObjects) {
#recursivly get all nested groups inherited from sensitive groups - don't trust AdminCount=1
$AllSensitiveGroups += Get-NestedGroups -DN $GroupObject.Properties.distinguishedname
}
}
else {Write-Warning "Could not find group: $Group"}
}
}
Write-Verbose "Number of sensitive groups found: $($AllSensitiveGroups.Count)"
#========================================================================= Gathering users with SPN =========================================================================
Write-Verbose "Gathering user accounts associated with SPN"
#list of properties to retreive from AD
$Properies = "msDS-UserPasswordExpiryTimeComputed", "msDS-AllowedToDelegateTo", "msDS-SupportedEncryptionTypes", "samaccountname", "userprincipalname", "useraccountcontrol", "displayname", "memberof", "serviceprincipalname", "pwdlastset", "description"
foreach ($Property in $Properies) {
$Searcher.PropertiesToLoad.Add($Property) | Out-Null
}
#filter user accounts with SPN except krbtgt account
$Searcher.Filter = "(&(samAccountType=805306368)(servicePrincipalName=*)(!(samAccountName=krbtgt)))"
$UsersWithSPN = @()
foreach ($Path in $SearchList) {
$Searcher.SearchRoot = $Path
try {
$UsersWithSPN += $Searcher.FindAll()
}
catch {
Write-Warning "Could not communicate with the domain: $($Path -replace "LDAP://DC=" -replace ",DC=", ".") - Does it have trust?"
}
}
Write-Verbose "Number of users that contain SPN: $($UsersWithSPN.Count)"
# ========================================================================= Gathering info about users =========================================================================
$CurrentDate = Get-Date
$AllData = @()
foreach ($User in $UsersWithSPN) {
Write-Verbose "Gathering info about the user: $($User.Properties.displayname)"
#----------------------------------- Time stuff -----------------------------------
$CrackWindow = "N/A"
#if the user's password has expiration date (works with FGPP) - https://msdn.microsoft.com/en-us/library/cc223410.aspx
if ($user.Properties.'msds-userpasswordexpirytimecomputed' -ne 9223372036854775807) # 0x7FFFFFFFFFFFFFFF
{
$PasswordExpiryDate = [datetime]::FromFileTime([string]$User.Properties.'msds-userpasswordexpirytimecomputed')
Write-Verbose "$($User.Properties.displayname)'s password will expire on $PasswordExpiryDate"
$CrackWindow = $PasswordExpiryDate.Subtract($CurrentDate).Days
Write-Verbose "Which means it has crack window of $CrackWindow days"
}
$PasswordLastSet = [datetime]::FromFileTime([string]$User.Properties.pwdlastset)
$PasswordAge = $CurrentDate.Subtract($PasswordLastSet).Days
#----------------------------------- UAC stuff -----------------------------------
#reading UAC attributes using bitmask - https://support.microsoft.com/en-us/kb/305144
[int32]$UAC = [string]$User.Properties.useraccountcontrol
$IsEnabled = $true
#if the user is disabled or lockedout
if (($UAC -band 2) -eq 2 -or ($UAC -band 16) -eq 16) {$IsEnabled = $false} # 0x0002 / 0x0010
$IsPasswordExpires = $true
#if the user password never expires
if (($UAC -band 65536) -eq 65536) # 0x10000
{
$IsPasswordExpires = $false
$CrackWindow = "Indefinitely"
}
$Delegation = $false
$TargetServices = "None"
#if the user is trusted for Kerberos unconstrained delegation
if (($UAC -band 524288) -eq 524288) # 0x80000
{
$Delegation = "Unconstrained"
$TargetServices = "Any"
}
#if the user is trusted for Kerberos constrained delegation
elseif ($User.Properties.'msds-allowedtodelegateto')
{
$Delegation = "Constrained"
#if the user is trusted for Kerberos constrained delegation with protocol transition
if (($UAC -band 16777216) -eq 16777216) {$Delegation = "Protocol Transition"} # 0x1000000
$TargetServices = [array]$User.Properties.'msds-allowedtodelegateto'
}
$EncType = "RC4-HMAC"
[int32]$eType = [string]$User.Properties.'msds-supportedencryptiontypes'
#if the user supports AES encryptions (MS-KILE 2.2.6) - https://msdn.microsoft.com/en-us/library/cc220375.aspx
if ($eType)
{
if (($eType -band 16) -eq 16) {$EncType = "AES256-HMAC"} # 0x10
elseif (($eType -band 8) -eq 8) {$EncType = "AES128-HMAC"} # 0x08
}
else
{
#if the UF_USE_DES_KEY_ONLY bit is set (account can only use DES in Kerberos authentication)
if (($UAC -band 2097152) -eq 2097152) {$EncType = "DES"} #0x200000
}
#----------------------------------- SPN stuff -----------------------------------
$AccountRunUnder = @()
#arranging SPNs to <service>/<server> format - https://technet.microsoft.com/en-us/library/cc961723.aspx
[array]$SPNs = $User.Properties.serviceprincipalname -replace ":.*" | Get-Unique
foreach ($SPN in $SPNs)
{
#splitting SPN to service type and instance name
$SPN = $SPN -split("/")
#TODO: More services and ports, take the port from the SPN
[array]$Service = switch -Wildcard ([string]$SPN[0])
{
"MSSQL*" {"MS SQL",@(1433)}
"HTTP" {"Web",@(80,443,8080)}
"WWW" {"Web",@(80,443,8080)}
"TERMSRV" {"Terminal Services",@(3389)}
"MONGO*" {"MongoDB Enterprise"}
"HOST" {"Computer services"}
"WSMAN" {"WinRM",@(5985,5986)}
"FTP" {"File Transfer",@(22)}
default {$SPN[0]}
}
$RunUnder = New-Object -TypeName psobject -Property @{
Service = $Service[0]
Server = $SPN[1]
IsAccessible = "N/A"
} | select Service,Server,IsAccessible
if (!$Stealth)
{
#if the service contains default ports
if ($Service[1])
{
$Socket = New-Object System.Net.Sockets.TcpClient
$RunUnder.IsAccessible = "No"
#testing if the service default ports are open on the server
foreach ($Port in $Service[1]) {
Write-Verbose "Checking connectivity to server: $($RunUnder.Server) on port $Port"
try {
$Socket.Connect($RunUnder.Server,$Port)
$RunUnder.IsAccessible = "Yes"
break
}
catch {
Write-Verbose "Port $Port is not accessiable on server: $($RunUnder.Server)"
}
}
}
else
{
Write-Verbose "Checking connectivity to server: $($RunUnder.Server)"
#if the server answers to one ping
if (Test-Connection -ComputerName $RunUnder.Server -Quiet -Count 1)
{
$RunUnder.IsAccessible = "Yes"
}
else
{
Write-Verbose "The server: $($RunUnder.Server) is not accessiable - Is it exist?"
$RunUnder.IsAccessible = "No"
}
}
}
$AccountRunUnder += $RunUnder
}
if ($User.Properties.memberof)
{
#get sensitive groups that the user is a memberof
$UserSensitiveGroups = (@(Compare-Object $AllSensitiveGroups $([array]$User.Properties.memberof) -IncludeEqual -ExcludeDifferent)).InputObject
}
$IsSensitive = $false
#if the user is a member of a sensitive group or is allowed for Kerberos unconstrained or S4U2Self delegation
if ($UserSensitiveGroups -or $Delegation)
{
Write-Verbose "$($User.Properties.displayname) is sensitive"
$IsSensitive = $true
}
$UserData = New-Object psobject -Property @{
UserName = [string]$User.Properties.samaccountname
DomainName = [string]$User.Properties.userprincipalname -replace ".*@"
IsSensitive = $IsSensitive
EncType = $EncType
Description = [string]$User.Properties.description
IsEnabled = $IsEnabled
IsPwdExpires = $IsPasswordExpires
PwdAge = $PasswordAge
CrackWindow = $CrackWindow
SensitiveGroups = $UserSensitiveGroups -replace "CN=" -replace ",.*"
MemberOf = $User.Properties.memberof -replace "CN=" -replace ",.*"
DelegationType = $Delegation
TargetServices = $TargetServices
NumofServers = ($AccountRunUnder.Server | select -Unique).Count
RunsUnder = $AccountRunUnder
AssociatedSPNs = [array]$User.Properties.serviceprincipalname
} | select UserName,DomainName,IsSensitive,EncType,Description,IsEnabled,IsPwdExpires,PwdAge,CrackWindow,SensitiveGroups,MemberOf,DelegationType,TargetServices,NumofServers,RunsUnder,AssociatedSPNs
$AllData += $UserData
}
if ($Sensitive)
{
Write-Verbose "Removing non-sensitive users from the list"
$AllData = $AllData | ? {$_.IsSensitive}
}
Write-Verbose "Number of users included in the list: $($AllData.UserName.Count)"
if ($GetSPNs) {return @($AllData.AssociatedSPNs)}
elseif ($FullData) {return $AllData}
else {return $AllData | ? {$_.IsEnabled} | Select-Object UserName,DomainName,IsSensitive,EncType,Description,PwdAge,CrackWindow,RunsUnder}
}