ActiveDirectory/Test-ADSecurityPosture.ps1
|
function Test-ADSecurityPosture { <# .SYNOPSIS Performs comprehensive Active Directory security assessment .DESCRIPTION Tests AD security posture including domain policies, GPO settings, trust relationships, and overall security configuration .PARAMETER Domain Target domain .PARAMETER IncludeTrusts Check domain trusts .PARAMETER IncludeGPO Analyze Group Policy Objects .EXAMPLE Test-ADSecurityPosture Test-ADSecurityPosture -IncludeTrusts -IncludeGPO .OUTPUTS PSCustomObject with security posture assessment #> [CmdletBinding()] param( [Parameter()] [string]$Domain, [Parameter()] [switch]$IncludeTrusts, [Parameter()] [switch]$IncludeGPO ) begin { Write-Host "=== AD SECURITY POSTURE ASSESSMENT ===" -ForegroundColor Cyan if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) { throw "ActiveDirectory module required" } Import-Module ActiveDirectory -ErrorAction Stop if (-not $Domain) { $Domain = (Get-ADDomain).DNSRoot } Write-Host "Domain: $Domain" -ForegroundColor Yellow $assessment = [PSCustomObject]@{ AssessmentDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Domain = $Domain SecurityScore = 0 MaxScore = 100 Categories = @{ PasswordPolicy = @{} AccountPolicy = @{} DomainControllers = @{} Trusts = @{} GPOs = @{} } Findings = @() Recommendations = @() } } process { try { # 1. Password Policy Assessment Write-Host "`n[1/5] Assessing Password Policy..." -ForegroundColor Cyan $passwordPolicy = Get-ADDefaultDomainPasswordPolicy -Server $Domain $score = 0 $maxPolicyScore = 25 if ($passwordPolicy.ComplexityEnabled) { $score += 10 Write-Host " + Complexity: Enabled" -ForegroundColor Green } else { $assessment.Findings += [PSCustomObject]@{ Severity = 'Critical' Category = 'Password Policy' Finding = 'Password complexity is disabled' } Write-Host " - Complexity: Disabled" -ForegroundColor Red } if ($passwordPolicy.MinPasswordLength -ge 14) { $score += 10 Write-Host " + Min Length: $($passwordPolicy.MinPasswordLength) chars" -ForegroundColor Green } elseif ($passwordPolicy.MinPasswordLength -ge 12) { $score += 7 Write-Host " ~ Min Length: $($passwordPolicy.MinPasswordLength) chars (recommended: 14+)" -ForegroundColor Yellow } else { $assessment.Findings += [PSCustomObject]@{ Severity = 'High' Category = 'Password Policy' Finding = "Minimum password length is only $($passwordPolicy.MinPasswordLength) characters" } Write-Host " - Min Length: $($passwordPolicy.MinPasswordLength) chars" -ForegroundColor Red } if ($passwordPolicy.MaxPasswordAge.Days -le 90 -and $passwordPolicy.MaxPasswordAge.Days -gt 0) { $score += 5 Write-Host " + Max Age: $($passwordPolicy.MaxPasswordAge.Days) days" -ForegroundColor Green } else { Write-Host " ~ Max Age: $($passwordPolicy.MaxPasswordAge.Days) days" -ForegroundColor Yellow } $assessment.Categories.PasswordPolicy = @{ Score = $score MaxScore = $maxPolicyScore ComplexityEnabled = $passwordPolicy.ComplexityEnabled MinPasswordLength = $passwordPolicy.MinPasswordLength MaxPasswordAge = $passwordPolicy.MaxPasswordAge.Days MinPasswordAge = $passwordPolicy.MinPasswordAge.Days PasswordHistoryCount = $passwordPolicy.PasswordHistoryCount } $assessment.SecurityScore += $score # 2. Account Lockout Policy Write-Host "`n[2/5] Assessing Account Lockout Policy..." -ForegroundColor Cyan $lockoutScore = 0 $maxLockoutScore = 15 if ($passwordPolicy.LockoutThreshold -gt 0) { if ($passwordPolicy.LockoutThreshold -le 5) { $lockoutScore += 10 Write-Host " + Lockout Threshold: $($passwordPolicy.LockoutThreshold) attempts" -ForegroundColor Green } else { $lockoutScore += 5 Write-Host " ~ Lockout Threshold: $($passwordPolicy.LockoutThreshold) attempts" -ForegroundColor Yellow } if ($passwordPolicy.LockoutDuration.TotalMinutes -ge 15) { $lockoutScore += 5 Write-Host " + Lockout Duration: $($passwordPolicy.LockoutDuration.TotalMinutes) minutes" -ForegroundColor Green } } else { $assessment.Findings += [PSCustomObject]@{ Severity = 'High' Category = 'Account Policy' Finding = 'Account lockout is disabled' } Write-Host " - Account Lockout: Disabled" -ForegroundColor Red } $assessment.Categories.AccountPolicy = @{ Score = $lockoutScore MaxScore = $maxLockoutScore LockoutThreshold = $passwordPolicy.LockoutThreshold LockoutDuration = $passwordPolicy.LockoutDuration.TotalMinutes LockoutWindow = $passwordPolicy.LockoutObservationWindow.TotalMinutes } $assessment.SecurityScore += $lockoutScore # 3. Domain Controllers Write-Host "`n[3/5] Checking Domain Controllers..." -ForegroundColor Cyan $dcs = @(Get-ADDomainController -Filter * -Server $Domain) $dcScore = 0 $maxDCScore = 20 Write-Host " Found $($dcs.Count) Domain Controllers" -ForegroundColor White foreach ($dc in $dcs) { if ($dc.IsReadOnly) { Write-Host " ~ $($dc.Name) [RODC]" -ForegroundColor Yellow } else { Write-Host " + $($dc.Name)" -ForegroundColor Green } } if ($dcs.Count -ge 2) { $dcScore += 10 } # Check for Server 2012 or older foreach ($dc in $dcs) { $os = $dc.OperatingSystem if ($os -match "2012|2008") { $assessment.Findings += [PSCustomObject]@{ Severity = 'High' Category = 'Domain Controllers' Finding = "$($dc.Name) running unsupported OS: $os" } } } $dcScore += 10 $assessment.Categories.DomainControllers = @{ Score = $dcScore MaxScore = $maxDCScore Count = $dcs.Count Controllers = $dcs.Name } $assessment.SecurityScore += $dcScore # 4. Domain Trusts if ($IncludeTrusts) { Write-Host "`n[4/5] Analyzing Domain Trusts..." -ForegroundColor Cyan $trusts = @(Get-ADTrust -Filter * -Server $Domain) $trustScore = 0 $maxTrustScore = 20 if ($trusts) { Write-Host " Found $($trusts.Count) trust(s)" -ForegroundColor White foreach ($trust in $trusts) { $trustType = $trust.Direction $trustColor = if ($trust.Direction -eq 'Bidirectional') { 'Yellow' } else { 'Green' } Write-Host " $($trust.Name) [$trustType]" -ForegroundColor $trustColor if ($trust.Direction -eq 'Bidirectional') { $assessment.Findings += [PSCustomObject]@{ Severity = 'Medium' Category = 'Trusts' Finding = "Bidirectional trust with $($trust.Name)" } } } $trustScore += 10 } else { Write-Host " No domain trusts configured" -ForegroundColor Green $trustScore += 20 } $assessment.Categories.Trusts = @{ Score = $trustScore MaxScore = $maxTrustScore Count = $trusts.Count Trusts = $trusts } $assessment.SecurityScore += $trustScore } # 5. GPO Analysis if ($IncludeGPO) { Write-Host "`n[5/5] Analyzing Group Policy Objects..." -ForegroundColor Cyan $gpos = @(Get-GPO -All -Domain $Domain) $gpoScore = 0 $maxGPOScore = 20 Write-Host " Found $($gpos.Count) GPOs" -ForegroundColor White # Check for unlinked GPOs $unlinkedGPOs = @($gpos | Where-Object { (Get-GPOReport -Guid $_.Id -ReportType Xml -Domain $Domain) -notmatch '<LinksTo>' }) if ($unlinkedGPOs.Count -gt 0) { Write-Host " ~ $($unlinkedGPOs.Count) unlinked GPOs found" -ForegroundColor Yellow $assessment.Findings += [PSCustomObject]@{ Severity = 'Low' Category = 'GPO' Finding = "$($unlinkedGPOs.Count) unlinked GPOs (cleanup recommended)" } } $gpoScore += 20 $assessment.Categories.GPOs = @{ Score = $gpoScore MaxScore = $maxGPOScore TotalCount = $gpos.Count UnlinkedCount = $unlinkedGPOs.Count } $assessment.SecurityScore += $gpoScore } # Calculate final score $assessment.SecurityScore = [math]::Round(($assessment.SecurityScore / $assessment.MaxScore) * 100, 0) # Generate recommendations if ($passwordPolicy.MinPasswordLength -lt 14) { $assessment.Recommendations += "Increase minimum password length to 14+ characters" } if ($passwordPolicy.LockoutThreshold -eq 0) { $assessment.Recommendations += "Enable account lockout policy (5 attempts recommended)" } $assessment.Recommendations += "Implement tiered administrative model" $assessment.Recommendations += "Enable audit policies for AD changes" $assessment.Recommendations += "Regular security assessments (quarterly)" } catch { Write-Error "Error during AD security assessment: $_" throw } } end { Write-Host "`n=== ASSESSMENT COMPLETE ===" -ForegroundColor Green Write-Host "`nSecurity Score: $($assessment.SecurityScore)/100" -ForegroundColor $( if ($assessment.SecurityScore -ge 80) { 'Green' } elseif ($assessment.SecurityScore -ge 60) { 'Yellow' } else { 'Red' } ) Write-Host "`nCategory Scores:" -ForegroundColor Cyan foreach ($category in $assessment.Categories.GetEnumerator()) { if ($null -ne $category.Value.Score) { $percentage = [math]::Round(($category.Value.Score / $category.Value.MaxScore) * 100, 0) Write-Host " $($category.Name): $percentage%" -ForegroundColor White } } if ($assessment.Findings.Count -gt 0) { Write-Host "`nFindings: $($assessment.Findings.Count)" -ForegroundColor Yellow $assessment.Findings | Group-Object Severity | ForEach-Object { Write-Host " $($_.Name): $($_.Count)" -ForegroundColor $( switch ($_.Name) { 'Critical' { 'Red' } 'High' { 'Red' } 'Medium' { 'Yellow' } default { 'White' } } ) } } if ($assessment.Recommendations.Count -gt 0) { Write-Host "`nRecommendations:" -ForegroundColor Cyan $assessment.Recommendations | ForEach-Object { Write-Host " ! $_" -ForegroundColor Yellow } } return $assessment } } |