Skip to content

Commit e5852ef

Browse files
authored
Update MSADPT_enumerate_dc2.ps1 for enhanced logging
1 parent 168b4bf commit e5852ef

1 file changed

Lines changed: 272 additions & 14 deletions

File tree

Azure Active Directory/MSADPT/MSADPT_enumerate_dc2.ps1

Lines changed: 272 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,31 @@
3333
Domain Controller / ADWS-capable server to use for all AD queries
3434
(e.g. dc01.foo.bar).
3535
36+
.OUTPUTS
37+
- MSADPT_DC_Details_<DCName>_<timestamp>.csv
38+
Detailed Domain Controller information.
39+
40+
- MSADPT_DC_Kerberoastable_<DCName>_<timestamp>.csv
41+
User accounts with Service Principal Names.
42+
Consumed by MSADPT_exploit_privesc_initial.ps1.
43+
Required downstream columns:
44+
- SamAccountName
45+
- ServicePrincipalName
46+
47+
- MSADPT_DC_ASREPRoastable_<DCName>_<timestamp>.csv
48+
User accounts with Kerberos pre-authentication disabled.
49+
Consumed by MSADPT_exploit_privesc_initial.ps1.
50+
Required downstream columns:
51+
- SamAccountName
52+
- UserPrincipalName
53+
54+
- MSADPT_DC_LAPSInfo_<DCName>_<timestamp>.csv
55+
LAPS-related computer records and reader group information.
56+
Consumed by MSADPT_exploit_privesc_initial.ps1.
57+
Required downstream columns:
58+
- DCName
59+
- LAPS_ComputerDetails
60+
3661
.EXAMPLE
3762
.\MSADPT_enumerate_dc2.ps1 `
3863
-InputDcCsvPath "C:\temp\MSADPT_Output\MSADPT_DCs.csv" `
@@ -45,23 +70,27 @@
4570
param(
4671
[Parameter(Mandatory)]
4772
[ValidateScript({
48-
if (-not (Test-Path $_)) {
73+
if (-not (Test-Path -LiteralPath $_ -PathType Leaf)) {
4974
throw "Input file '$_' does not exist."
5075
}
5176
$true
5277
})]
5378
[string]$InputDcCsvPath,
5479

5580
[Parameter(Mandatory)]
81+
[ValidateNotNullOrEmpty()]
5682
[string]$OutputBaseDir,
5783

5884
[Parameter(Mandatory)]
85+
[ValidateNotNull()]
5986
[PSCredential]$Credential,
6087

6188
[Parameter(Mandatory)]
89+
[ValidateNotNullOrEmpty()]
6290
[string]$DomainFQDN,
6391

6492
[Parameter(Mandatory)]
93+
[ValidateNotNullOrEmpty()]
6594
[string]$AdServer
6695
)
6796

@@ -70,28 +99,24 @@ param(
7099
# ---------------------------------------------------------------------
71100
$helpersModulePath = Join-Path $PSScriptRoot 'MSADPT.Helpers.psm1'
72101

73-
if (-not (Test-Path -LiteralPath $helpersModulePath)) {
102+
if (-not (Test-Path -LiteralPath $helpersModulePath -PathType Leaf)) {
74103
Write-Error "Required helper module not found at '$helpersModulePath'. Aborting."
75104
exit 1
76105
}
77106

78107
Import-Module $helpersModulePath -Force -ErrorAction Stop
79108

80-
Write-MSADPTLog -Message "MSADPT_enumerate_dc.ps1 starting." -Level 'INFO'
109+
# ---------------------------------------------------------------------
110+
# Runtime state
111+
# ---------------------------------------------------------------------
112+
$ScriptStartTime = Get-Date -Format "yyyyMMdd"
113+
114+
Write-MSADPTLog -Message "MSADPT_enumerate_dc2.ps1 starting." -Level 'INFO'
81115
Write-MSADPTLog -Message "DomainFQDN : $DomainFQDN"
82116
Write-MSADPTLog -Message "AdServer : $AdServer"
83117
Write-MSADPTLog -Message "Input CSV : $InputDcCsvPath"
84118
Write-MSADPTLog -Message "Output Dir : $OutputBaseDir"
85119

86-
# ---------------------------------------------------------------------
87-
# Pre-flight: AD connectivity check
88-
# ---------------------------------------------------------------------
89-
$rootDSE = Test-MSADPTADConnectivity -Credential $Credential -AdServer $AdServer
90-
if (-not $rootDSE) {
91-
Write-MSADPTLog -Message "Active Directory connectivity pre-flight failed. Aborting." -Level 'ERROR'
92-
exit 1
93-
}
94-
95120
# ---------------------------------------------------------------------
96121
# Ensure output directory exists
97122
# ---------------------------------------------------------------------
@@ -108,6 +133,39 @@ if (-not $DCs) {
108133
exit 0
109134
}
110135

136+
$requiredColumns = @('Name', 'IPv4Address')
137+
$actualColumns = @($DCs[0].PSObject.Properties.Name)
138+
139+
foreach ($column in $requiredColumns) {
140+
if ($actualColumns -notcontains $column) {
141+
Write-MSADPTLog -Message "Input CSV is missing required column '$column'. Aborting." -Level 'ERROR'
142+
exit 1
143+
}
144+
}
145+
146+
147+
$RequiredModules = @('ActiveDirectory')
148+
149+
foreach ($module in $RequiredModules) {
150+
if (-not (Get-Module -ListAvailable -Name $module)) {
151+
Write-MSADPTLog -Message "Required module '$module' is not available. Aborting." -Level 'ERROR'
152+
exit 1
153+
}
154+
155+
if (-not (Get-Module -Name $module)) {
156+
Import-Module $module -ErrorAction Stop
157+
Write-MSADPTLog -Message "Imported module '$module'." -Level 'INFO'
158+
}
159+
}
160+
161+
$adSplat = New-MSADPTAdCommandSplat -Server $AdServer -Credential $Credential
162+
163+
$rootDSE = Test-MSADPTADConnectivity -Credential $Credential -AdServer $AdServer
164+
if (-not $rootDSE) {
165+
Write-MSADPTLog -Message "Active Directory connectivity pre-flight failed. Aborting." -Level 'ERROR'
166+
exit 1
167+
}
168+
111169
# ---------------------------------------------------------------------
112170
# Iterate each Domain Controller
113171
# ---------------------------------------------------------------------
@@ -163,7 +221,7 @@ foreach ($DC in $DCs) {
163221

164222
$DCInfo |
165223
Select-Object Name, HostName, IPv4Address, OperatingSystem, Site, IsGlobalCatalog, IsReadOnly |
166-
Export-Csv -Path (Join-Path $CurrentDCOutputDir "MSADPT_DC_Details_$DCName.csv") -NoTypeInformation -Force
224+
Export-Csv -Path (Join-Path $CurrentDCOutputDir "MSADPT_DC_Details_${DCName}_$ScriptStartTime.csv") -NoTypeInformation -Force
167225

168226
Write-MSADPTLog -Message "DC details saved for $DCName."
169227
}
@@ -172,6 +230,206 @@ foreach ($DC in $DCs) {
172230
$DCErrors += "DC info failed"
173231
}
174232
}
233+
# -----------------------------------------------------------------
234+
# 3. Kerberoastable Account Discovery
235+
# -----------------------------------------------------------------
236+
if (Prompt-User -PromptText "Identify Kerberoastable accounts (user accounts with SPNs) from ${DCName}?") {
237+
Write-MSADPTLog -Message "Identifying Kerberoastable accounts using explicit AD server '$AdServer'." -Level 'INFO'
238+
239+
try {
240+
$KerberoastableAccounts = @(
241+
Get-ADUser @adSplat `
242+
-LDAPFilter '(&(objectCategory=person)(objectClass=user)(servicePrincipalName=*))' `
243+
-Properties ServicePrincipalName, UserPrincipalName, Enabled, LastLogonDate, PasswordLastSet, DistinguishedName `
244+
-ErrorAction Stop
245+
)
246+
247+
if ($KerberoastableAccounts.Count -gt 0) {
248+
$KRBCsvPath = Join-Path $CurrentDCOutputDir "MSADPT_DC_Kerberoastable_${DCName}_$ScriptStartTime.csv"
249+
250+
$KerberoastableAccounts |
251+
Select-Object `
252+
Name,
253+
SamAccountName,
254+
UserPrincipalName,
255+
@{
256+
Name = 'ServicePrincipalName'
257+
Expression = { @($_.ServicePrincipalName) -join ';' }
258+
},
259+
Enabled,
260+
LastLogonDate,
261+
PasswordLastSet,
262+
DistinguishedName |
263+
Export-Csv -Path $KRBCsvPath -NoTypeInformation -Force
264+
265+
Write-MSADPTLog -Message "Identified $($KerberoastableAccounts.Count) Kerberoastable account(s). Details saved to '$KRBCsvPath'." -Level 'WARNING'
266+
}
267+
else {
268+
Write-MSADPTLog -Message "No Kerberoastable accounts found." -Level 'INFO'
269+
}
270+
}
271+
catch {
272+
Write-MSADPTLog -Message "Failed to identify Kerberoastable accounts from ${DCName}: $($_.Exception.Message)" -Level 'ERROR'
273+
$DCErrors += "Kerberoastable account discovery failed"
274+
}
275+
}
276+
else {
277+
Write-MSADPTLog -Message "Skipping Kerberoastable account identification for $DCName." -Level 'INFO'
278+
}
279+
280+
# -----------------------------------------------------------------
281+
# 4. AS-REP Roastable Account Discovery
282+
# -----------------------------------------------------------------
283+
if (Prompt-User -PromptText "Identify AS-REP Roastable accounts (DONT_REQ_PREAUTH) from ${DCName}?") {
284+
Write-MSADPTLog -Message "Identifying AS-REP Roastable accounts using explicit AD server '$AdServer'." -Level 'INFO'
285+
286+
try {
287+
# 4194304 / 0x400000 = DONT_REQ_PREAUTH
288+
$ASRepRoastableAccounts = @(
289+
Get-ADUser @adSplat `
290+
-LDAPFilter '(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))' `
291+
-Properties UserPrincipalName, ServicePrincipalName, UserAccountControl, Enabled, LastLogonDate, PasswordLastSet, DistinguishedName `
292+
-ErrorAction Stop
293+
)
294+
295+
if ($ASRepRoastableAccounts.Count -gt 0) {
296+
$ASRCsvPath = Join-Path $CurrentDCOutputDir "MSADPT_DC_ASREPRoastable_${DCName}_$ScriptStartTime.csv"
297+
298+
$ASRepRoastableAccounts |
299+
Select-Object `
300+
Name,
301+
SamAccountName,
302+
UserPrincipalName,
303+
@{
304+
Name = 'ServicePrincipalName'
305+
Expression = { @($_.ServicePrincipalName) -join ';' }
306+
},
307+
UserAccountControl,
308+
Enabled,
309+
LastLogonDate,
310+
PasswordLastSet,
311+
DistinguishedName |
312+
Export-Csv -Path $ASRCsvPath -NoTypeInformation -Force
313+
314+
Write-MSADPTLog -Message "Identified $($ASRepRoastableAccounts.Count) AS-REP Roastable account(s). Details saved to '$ASRCsvPath'." -Level 'WARNING'
315+
}
316+
else {
317+
Write-MSADPTLog -Message "No AS-REP Roastable accounts found." -Level 'INFO'
318+
}
319+
}
320+
catch {
321+
Write-MSADPTLog -Message "Failed to identify AS-REP Roastable accounts from ${DCName}: $($_.Exception.Message)" -Level 'ERROR'
322+
$DCErrors += "AS-REP Roastable account discovery failed"
323+
}
324+
}
325+
else {
326+
Write-MSADPTLog -Message "Skipping AS-REP Roastable account identification for $DCName." -Level 'INFO'
327+
}
328+
329+
# -----------------------------------------------------------------
330+
# 5. LAPS Configuration / Exposure Review
331+
# -----------------------------------------------------------------
332+
if (Prompt-User -PromptText "Enumerate LAPS configuration and readable password attributes from ${DCName}?") {
333+
Write-MSADPTLog -Message "Querying LAPS-related computer attributes using explicit AD server '$AdServer'." -Level 'INFO'
334+
335+
try {
336+
$schemaNC = $rootDSE.schemaNamingContext
337+
338+
$HasWindowsLAPS = $false
339+
try {
340+
$HasWindowsLAPS = $null -ne (
341+
Get-ADObject @adSplat `
342+
-SearchBase $schemaNC `
343+
-LDAPFilter '(lDAPDisplayName=msLAPS-Password)' `
344+
-ErrorAction SilentlyContinue
345+
)
346+
}
347+
catch {
348+
Write-MSADPTLog -Message "Could not confirm Windows LAPS schema support: $($_.Exception.Message)" -Level 'WARNING'
349+
$HasWindowsLAPS = $false
350+
}
351+
352+
$LAPSProperties = @(
353+
'ms-Mcs-AdmPwd',
354+
'ms-Mcs-AdmPwdExpirationTime'
355+
)
356+
357+
$LAPSLdapFilter = '(ms-Mcs-AdmPwdExpirationTime=*)'
358+
359+
if ($HasWindowsLAPS) {
360+
$LAPSProperties += @(
361+
'msLAPS-Password',
362+
'msLAPS-PasswordExpirationTime'
363+
)
364+
365+
$LAPSLdapFilter = '(|(ms-Mcs-AdmPwdExpirationTime=*)(msLAPS-PasswordExpirationTime=*))'
366+
}
367+
368+
$LAPSComputers = @(
369+
Get-ADComputer @adSplat `
370+
-LDAPFilter $LAPSLdapFilter `
371+
-Properties $LAPSProperties `
372+
-ErrorAction Stop
373+
)
374+
375+
$LAPSReaders = @()
376+
try {
377+
$LAPSReaders = @(
378+
Get-ADGroup @adSplat `
379+
-Filter "Name -like '*LAPS*Reader*'" `
380+
-Properties Member `
381+
-ErrorAction Stop
382+
)
383+
}
384+
catch {
385+
Write-MSADPTLog -Message "Could not enumerate LAPS reader groups: $($_.Exception.Message)" -Level 'WARNING'
386+
$LAPSReaders = @()
387+
}
388+
389+
$LAPSInfo = [PSCustomObject]@{
390+
Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
391+
DCName = $DCName
392+
WindowsLAPS_Supported = $HasWindowsLAPS
393+
LAPS_EnabledComputers = @($LAPSComputers).Count
394+
LAPS_ComputerDetails = (
395+
$LAPSComputers |
396+
Select-Object `
397+
Name,
398+
DNSHostName,
399+
DistinguishedName,
400+
'ms-Mcs-AdmPwd',
401+
'ms-Mcs-AdmPwdExpirationTime',
402+
'msLAPS-Password',
403+
'msLAPS-PasswordExpirationTime' |
404+
ConvertTo-Json -Compress -Depth 5
405+
)
406+
LAPS_ReaderGroups = (
407+
$LAPSReaders |
408+
Select-Object `
409+
Name,
410+
SamAccountName,
411+
DistinguishedName,
412+
@{
413+
Name = 'Members'
414+
Expression = { @($_.Member) -join ';' }
415+
} |
416+
ConvertTo-Json -Compress -Depth 5
417+
)
418+
}
419+
420+
$LAPSCsvPath = Join-Path $CurrentDCOutputDir "MSADPT_DC_LAPSInfo_${DCName}_$ScriptStartTime.csv"
421+
$LAPSInfo | Export-Csv -Path $LAPSCsvPath -NoTypeInformation -Force -ErrorAction Stop
422+
423+
Write-MSADPTLog -Message "LAPS configuration details saved to '$LAPSCsvPath'. Password fields are populated only if the supplied credential has read rights." -Level 'WARNING'
424+
}
425+
catch {
426+
Write-MSADPTLog -Message "Failed to enumerate LAPS configuration from ${DCName}: $($_.Exception.Message)" -Level 'ERROR'
427+
$DCErrors += "LAPS enumeration failed"
428+
}
429+
}
430+
else {
431+
Write-MSADPTLog -Message "Skipping LAPS configuration enumeration for $DCName." -Level 'INFO'
432+
}
175433

176434
# -----------------------------------------------------------------
177435
# Final DC summary
@@ -187,4 +445,4 @@ foreach ($DC in $DCs) {
187445
}
188446
}
189447

190-
Write-MSADPTLog -Message "MSADPT_enumerate_dc.ps1 completed." -Level 'INFO'
448+
Write-MSADPTLog -Message "MSADPT_enumerate_dc2.ps1 completed." -Level 'INFO'

0 commit comments

Comments
 (0)