#Requires -Modules ActiveDirectory
# AD Security Audit Tool — letzte Aktualisierung 08.2025
<#
.SYNOPSIS
Active Directory Sicherheitsaudit-Skript
.DESCRIPTION
Führt eine umfassende Überprüfung auf Schwachstellen und Fehlkonfigurationen im Active Directory durch
.AUTHOR
AD Security Audit Tool
#>
[CmdletBinding()]
param(
[string]$OutputPath = "",
[switch]$IncludePasswordPolicy,
[switch]$CheckGroupMemberships,
[switch]$ExportToCSV
)
# Erstelle Standard-Ausgabeordner falls nicht angegeben
if ([string]::IsNullOrEmpty($OutputPath)) {
$defaultFolder = "C:\Schwachstellen_AD"
# Prüfe ob Ordner existiert, wenn nicht erstelle ihn
if (-not (Test-Path $defaultFolder)) {
try {
New-Item -Path $defaultFolder -ItemType Directory -Force | Out-Null
Write-Host "Ausgabeordner erstellt: $defaultFolder" -ForegroundColor Green
} catch {
Write-Error "Konnte Ausgabeordner nicht erstellen: $defaultFolder. Fehler: $($_.Exception.Message)"
$defaultFolder = $env:TEMP
Write-Warning "Verwende Temp-Ordner als Alternative: $defaultFolder"
}
} else {
Write-Host "Verwende existierenden Ausgabeordner: $defaultFolder" -ForegroundColor Gray
}
$OutputPath = Join-Path $defaultFolder "AD_Security_Audit_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
} else {
# Wenn ein benutzerdefinierter Pfad angegeben wurde, prüfe den Ordner
$directory = Split-Path $OutputPath -Parent
if (-not (Test-Path $directory)) {
try {
New-Item -Path $directory -ItemType Directory -Force | Out-Null
Write-Host "Ausgabeordner erstellt: $directory" -ForegroundColor Green
} catch {
Write-Error "Konnte Ausgabeordner nicht erstellen: $directory. Fehler: $($_.Exception.Message)"
exit 1
}
}
}
# Prüfung ob AD-Module verfügbar ist
if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
Write-Error "ActiveDirectory PowerShell-Modul ist nicht verfügbar. Bitte installieren Sie die RSAT-Tools."
exit 1
}
Import-Module ActiveDirectory
# HTML-Ausgabe Funktionen
function New-HTMLReport {
param([string]$Title)
return @"
$Title
"@
}
function Add-HTMLSection {
param([string]$Title, [string]$Content, [string]$Type = "info")
return @"
$Title
$Content
"@
}
function Close-HTMLReport {
return ""
}
# Hilfsfunktionen für Empfehlungen
function Get-AdminAccountRecommendations {
param($AdminAccounts)
$recommendations = @()
$highRiskAccounts = $AdminAccounts | Where-Object { $_.PasswordNeverExpires -eq $true }
$disabledAdmins = $AdminAccounts | Where-Object { $_.Enabled -eq $false }
$oldPasswords = $AdminAccounts | Where-Object { $_.PasswordLastSet -lt (Get-Date).AddDays(-90) }
if ($highRiskAccounts.Count -gt 0) {
$recommendations += @"
🔴 SOFORTMASSNAHME: $($highRiskAccounts.Count) Administrator-Konto(en) mit "Kennwort läuft nie ab" gefunden!
Aktion: Passwort-Ablaufrichtlinie aktivieren
# Passwort-Ablauf für Benutzer aktivieren:
Set-ADUser -Identity "BENUTZERNAME" -PasswordNeverExpires `$false
"@
}
if ($disabledAdmins.Count -gt 0) {
$recommendations += @"
🟡 ÜBERPRÜFUNG: $($disabledAdmins.Count) deaktivierte(s) Administrator-Konto(en) gefunden.
Aktion: Prüfen Sie, ob diese Konten noch benötigt werden. Falls nicht, aus privilegierten Gruppen entfernen.
"@
}
if ($oldPasswords.Count -gt 0) {
$recommendations += @"
🟡 SICHERHEITSRISIKO: $($oldPasswords.Count) Administrator-Konto(en) mit alten Passwörtern (>90 Tage).
Aktion: Passwort-Wechsel erzwingen
# Passwort-Wechsel bei nächster Anmeldung erzwingen:
Set-ADUser -Identity "BENUTZERNAME" -ChangePasswordAtLogon `$true
"@
}
$recommendations += @"
📋 Allgemeine Empfehlungen für Administrator-Konten anzeigen
Best Practices für Administrator-Konten:
Minimale Privilegien: Nur notwendige Berechtigungen vergeben
Separate Admin-Konten: Keine täglichen Arbeiten mit Admin-Konten
Starke Passwörter: Mindestens 14 Zeichen, Komplexität aktiviert
Regelmäßige Überprüfung: Monatliche Kontrolle der Gruppenmitgliedschaften
MFA aktivieren: Multi-Faktor-Authentifizierung für alle Admin-Konten
Logging: Alle Admin-Aktivitäten protokollieren
Empfohlene PowerShell-Befehle:
# Alle Admin-Gruppen-Mitglieder anzeigen:
Get-ADGroupMember "Domain Admins" | Get-ADUser -Properties LastLogonDate
# Passwort-Richtlinie prüfen:
Get-ADDefaultDomainPasswordPolicy
# Admin-Konto aus Gruppe entfernen:
Remove-ADGroupMember -Identity "Domain Admins" -Members "BENUTZERNAME"
"@
return $recommendations -join "`n"
}
function Get-StaleAccountRecommendations {
param($StaleAccounts)
if ($StaleAccounts.Count -eq 0) {
return "✅ Keine verwaisten Benutzerkonten gefunden. Gut gemacht!
"
}
$recommendations = @()
$recommendations += @"
🟡 AUFRÄUMEN ERFORDERLICH: $($StaleAccounts.Count) verwaiste(s) Benutzerkonto(en) gefunden (>90 Tage inaktiv).
Aktion: Konten überprüfen und bei Bedarf deaktivieren/löschen
📋 Empfehlungen für verwaiste Konten anzeigen
Vorgehen bei verwaisten Konten:
Schritt 1: Mit Vorgesetzten/HR klären ob Mitarbeiter noch im Unternehmen
Schritt 2: Konto zunächst deaktivieren (nicht sofort löschen!)
Schritt 3: Nach 30 Tagen Wartezeit endgültig löschen
Schritt 4: Mailbox und Dateien entsprechend Richtlinien archivieren
PowerShell-Befehle:
# Benutzer deaktivieren:
Disable-ADAccount -Identity "BENUTZERNAME"
# Benutzer in "Deaktivierte Benutzer" OU verschieben:
Move-ADObject -Identity "CN=BENUTZER,OU=Users,DC=domain,DC=com" -TargetPath "OU=Disabled Users,DC=domain,DC=com"
# Alle inaktiven Konten finden (>90 Tage):
Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly
"@
return $recommendations -join "`n"
}
function Get-ServiceAccountRecommendations {
param($ServiceAccounts)
if ($ServiceAccounts.Count -eq 0) {
return "✅ Keine vulnerablen Service-Konten gefunden.
"
}
return @"
🔴 KERBEROASTING-RISIKO: $($ServiceAccounts.Count) vulnerable(s) Service-Konto(en) gefunden!
Sofortmaßnahmen erforderlich!
🛡️ Service-Account Sicherheitsmaßnahmen anzeigen
Sofortmaßnahmen:
Passwörter sofort ändern (mindestens 25 Zeichen)
Managed Service Accounts (MSA) implementieren wo möglich
Minimale Berechtigungen vergeben
AES-Verschlüsselung erzwingen
# Managed Service Account erstellen:
New-ADServiceAccount -Name "MyServiceMSA" -DNSHostName "server.domain.com"
# Service Account Passwort ändern:
Set-ADAccountPassword -Identity "ServiceAccount" -Reset
# AES-Verschlüsselung erzwingen:
Set-ADUser -Identity "ServiceAccount" -KerberosEncryptionType AES128,AES256
"@
}
function Get-ComputerAccountRecommendations {
param($ComputerAccounts)
if ($ComputerAccounts.Count -eq 0) {
return "✅ Keine verwaisten Computer-Konten gefunden.
"
}
return @"
🟡 AUFRÄUMEN: $($ComputerAccounts.Count) verwaiste(s) Computer-Konto(en) gefunden (>60 Tage inaktiv).
Aktion: Computer-Konten überprüfen und nicht mehr verwendete löschen
💻 Computer-Account Verwaltung anzeigen
Vorgehen:
Physische Existenz prüfen: Ist der Computer noch vorhanden?
Netzwerk-Status: Ist der Computer noch im Netzwerk aktiv?
Deaktivieren vor Löschen: Zunächst deaktivieren, nach 30 Tagen löschen
# Computer deaktivieren:
Disable-ADAccount -Identity "COMPUTERNAME`$"
# Inaktive Computer finden:
Search-ADAccount -AccountInactive -TimeSpan 60.00:00:00 -ComputersOnly
# Computer-Konto löschen:
Remove-ADComputer -Identity "COMPUTERNAME" -Confirm
"@
}
function Get-GeneralSecurityRecommendations {
return @"
🔒 Allgemeine AD-Sicherheitsempfehlungen anzeigen
Regelmäßige Sicherheitsmaßnahmen:
Monatliche Audits: Führen Sie dieses Skript monatlich aus
Passwort-Richtlinien: Mindestens 12 Zeichen, Komplexität, 90 Tage Ablauf
Konto-Sperrung: Nach 5 fehlgeschlagenen Anmeldeversuchen
Privilegierte Zugriffe: Just-in-Time Admin Access implementieren
Überwachung: Anomalie-Erkennung für Admin-Aktivitäten
Backup: Regelmäßige AD-Backups und Recovery-Tests
Monitoring und Alerting:
# Event Log Monitoring für Admin-Gruppen-Änderungen:
Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4728,4729,4732,4733}
# Failed Logon Attempts:
Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625} | Where-Object {`$_.TimeCreated -gt (Get-Date).AddHours(-24)}
"@
}
function Get-AdminGroups {
# Deutsche und englische Gruppennamen
$groupMappings = @{
"Domain-Admins" = @("Domain Admins", "Domänen-Admins", "Domäne-Admins")
"Enterprise-Admins" = @("Enterprise Admins", "Organisations-Admins", "Unternehmens-Admins")
"Schema-Admins" = @("Schema Admins", "Schema-Admins")
"Administrators" = @("Administrators", "Administratoren")
"Backup-Operators" = @("Backup Operators", "Sicherungs-Operatoren")
"Account-Operators" = @("Account Operators", "Konto-Operatoren")
"Server-Operators" = @("Server Operators", "Server-Operatoren")
"Print-Operators" = @("Print Operators", "Druck-Operatoren")
}
$foundGroups = @{}
foreach ($category in $groupMappings.Keys) {
foreach ($groupName in $groupMappings[$category]) {
try {
$group = Get-ADGroup -Identity $groupName -ErrorAction Stop
$foundGroups[$category] = $group.Name
Write-Verbose "Gefunden: $($group.Name) für Kategorie $category"
break
} catch {
# Gruppe nicht gefunden, probiere nächste
continue
}
}
if (-not $foundGroups.ContainsKey($category)) {
Write-Verbose "Keine Gruppe für Kategorie $category gefunden"
}
}
return $foundGroups
}
# Audit-Funktionen
function Test-AdminAccounts {
Write-Host "Prüfe Administrator-Konten..." -ForegroundColor Green
# Ermittle verfügbare Admin-Gruppen
$adminGroups = Get-AdminGroups
$results = @()
if ($adminGroups.Count -eq 0) {
Write-Warning "Keine Administrator-Gruppen gefunden. Prüfe Well-Known SIDs..."
# Fallback: Verwende Well-Known SIDs
$wellKnownGroups = @{
"Domain Admins" = "S-1-5-21-*-512"
"Enterprise Admins" = "S-1-5-21-*-519"
"Schema Admins" = "S-1-5-21-*-518"
"Administrators" = "S-1-5-32-544"
}
foreach ($groupType in $wellKnownGroups.Keys) {
try {
$domain = Get-ADDomain
$sid = $wellKnownGroups[$groupType].Replace("*", $domain.DomainSID)
$group = Get-ADGroup -Identity $sid -ErrorAction Stop
$adminGroups[$groupType] = $group.Name
} catch {
Write-Verbose "Gruppe mit SID $sid nicht gefunden"
}
}
}
Write-Host "Gefundene Admin-Gruppen: $($adminGroups.Values -join ', ')" -ForegroundColor Cyan
foreach ($groupCategory in $adminGroups.Keys) {
$groupName = $adminGroups[$groupCategory]
try {
Write-Host " - Prüfe Gruppe: $groupName" -ForegroundColor Gray
$members = Get-ADGroupMember -Identity $groupName -Recursive -ErrorAction Stop
foreach ($member in $members) {
if ($member.objectClass -eq "user") {
$user = Get-ADUser -Identity $member.SamAccountName -Properties LastLogonDate, PasswordLastSet, Enabled, PasswordNeverExpires -ErrorAction Stop
$results += [PSCustomObject]@{
Group = $groupName
GroupCategory = $groupCategory
Username = $user.SamAccountName
DisplayName = $user.Name
Enabled = $user.Enabled
LastLogon = if ($user.LastLogonDate) { $user.LastLogonDate } else { "Nie" }
PasswordLastSet = if ($user.PasswordLastSet) { $user.PasswordLastSet } else { "Unbekannt" }
PasswordNeverExpires = $user.PasswordNeverExpires
RiskLevel = if ($user.PasswordNeverExpires) { "Hoch" } elseif (-not $user.Enabled) { "Niedrig" } else { "Mittel" }
}
}
}
} catch {
Write-Warning "Konnte Gruppe '$groupName' nicht abfragen: $($_.Exception.Message)"
}
}
if ($results.Count -eq 0) {
Write-Warning "Keine Administrator-Konten gefunden. Möglicherweise fehlen Berechtigungen."
}
return $results
}
function Test-StaleAccounts {
Write-Host "Suche verwaiste Benutzerkonten..." -ForegroundColor Green
$cutoffDate = (Get-Date).AddDays(-90)
try {
# Hole alle aktivierten Benutzerkonten
$allUsers = Get-ADUser -Filter "Enabled -eq 'True'" -Properties LastLogonDate, PasswordLastSet, Created -ErrorAction Stop
Write-Host " $($allUsers.Count) aktive Benutzerkonten gefunden." -ForegroundColor Gray
$staleUsers = $allUsers | Where-Object {
($_.LastLogonDate -lt $cutoffDate -or $_.LastLogonDate -eq $null) -and
$_.SamAccountName -notlike "*$" -and # Computer-Konten ausschließen
$_.SamAccountName -ne "krbtgt" -and # Service-Konten ausschließen
$_.SamAccountName -ne "Guest"
}
Write-Host " $($staleUsers.Count) verwaiste Konten identifiziert (>90 Tage inaktiv)." -ForegroundColor Gray
return $staleUsers | Select-Object SamAccountName, Name,
@{Name='LastLogon'; Expression={if ($_.LastLogonDate) {$_.LastLogonDate.ToString("dd.MM.yyyy")} else {"Nie"}}},
@{Name='PasswordLastSet'; Expression={if ($_.PasswordLastSet) {$_.PasswordLastSet.ToString("dd.MM.yyyy")} else {"Unbekannt"}}},
@{Name='Created'; Expression={$_.Created.ToString("dd.MM.yyyy")}},
Enabled
} catch {
Write-Warning "Fehler beim Abfragen der Benutzerkonten: $($_.Exception.Message)"
return @()
}
}
function Test-PasswordPolicy {
Write-Host "Prüfe Passwort-Richtlinien..." -ForegroundColor Green
$defaultPolicy = Get-ADDefaultDomainPasswordPolicy
$fineGrainedPolicies = Get-ADFineGrainedPasswordPolicy -Filter *
$policyResults = @{
DefaultPolicy = $defaultPolicy
FineGrainedPolicies = $fineGrainedPolicies
WeakPolicyUsers = @()
}
# Prüfe auf Benutzer mit schwachen Passwort-Einstellungen
$usersWithWeakPasswords = Get-ADUser -Filter * -Properties PasswordNeverExpires, PasswordNotRequired |
Where-Object { $_.PasswordNeverExpires -eq $true -or $_.PasswordNotRequired -eq $true }
$policyResults.WeakPolicyUsers = $usersWithWeakPasswords
return $policyResults
}
function Test-ServiceAccounts {
Write-Host "Suche Service-Konten mit Schwachstellen..." -ForegroundColor Green
$serviceAccounts = Get-ADUser -Filter * -Properties ServicePrincipalNames, PasswordLastSet, PasswordNeverExpires |
Where-Object { $_.ServicePrincipalNames -ne $null }
$vulnerableServices = @()
foreach ($account in $serviceAccounts) {
if ($account.PasswordNeverExpires -or
(Get-Date) - $account.PasswordLastSet -gt [TimeSpan]::FromDays(365)) {
$vulnerableServices += $account
}
}
return $vulnerableServices | Select-Object SamAccountName, Name, ServicePrincipalNames, PasswordLastSet, PasswordNeverExpires
}
function Test-ComputerAccounts {
Write-Host "Prüfe Computer-Konten..." -ForegroundColor Green
$cutoffDate = (Get-Date).AddDays(-60)
$staleComputers = Get-ADComputer -Filter * -Properties LastLogonDate, OperatingSystem, Enabled |
Where-Object {
$_.Enabled -eq $true -and
($_.LastLogonDate -lt $cutoffDate -or $_.LastLogonDate -eq $null)
}
return $staleComputers | Select-Object Name, LastLogonDate, OperatingSystem, Enabled
}
function Test-GPOSecurity {
Write-Host "Prüfe Group Policy Sicherheit..." -ForegroundColor Green
try {
Import-Module GroupPolicy -ErrorAction Stop
$gpos = Get-GPO -All
$gpoIssues = @()
foreach ($gpo in $gpos) {
# Prüfe auf unverknüpfte GPOs
$links = Get-GPOReport -Guid $gpo.Id -ReportType XML | Select-String "LinksTo"
if (-not $links) {
$gpoIssues += [PSCustomObject]@{
GPOName = $gpo.DisplayName
Issue = "Unverknüpfte GPO"
Severity = "Warning"
}
}
# Prüfe auf deaktivierte GPOs mit Links
if ($gpo.GpoStatus -eq "AllSettingsDisabled" -and $links) {
$gpoIssues += [PSCustomObject]@{
GPOName = $gpo.DisplayName
Issue = "Deaktivierte GPO mit aktiven Links"
Severity = "Warning"
}
}
}
return $gpoIssues
} catch {
Write-Warning "GroupPolicy-Modul nicht verfügbar. GPO-Prüfung wird übersprungen."
return @()
}
}
function Test-DomainTrusts {
Write-Host "Prüfe Domain-Trusts..." -ForegroundColor Green
try {
$trusts = Get-ADTrust -Filter * -ErrorAction Stop
$trustIssues = @()
if ($trusts.Count -eq 0) {
Write-Host " Keine Domain-Trusts gefunden." -ForegroundColor Gray
return @()
}
foreach ($trust in $trusts) {
$issue = $null
$severity = "Info"
if ($trust.TrustType -eq "External" -and $trust.Direction -eq "Bidirectional") {
$issue = "Bidirektionaler externer Trust (Sicherheitsrisiko)"
$severity = "Critical"
} elseif ($trust.Direction -eq "Bidirectional") {
$issue = "Bidirektionaler Trust"
$severity = "Warning"
} elseif ($trust.TrustType -eq "External") {
$issue = "Externer Trust"
$severity = "Warning"
}
if ($issue) {
$trustIssues += [PSCustomObject]@{
TrustedDomain = $trust.Name
Issue = $issue
Severity = $severity
Direction = $trust.Direction
Type = $trust.TrustType
Source = $trust.Source
Target = $trust.Target
}
}
}
Write-Host " $($trusts.Count) Trust(s) gefunden, $($trustIssues.Count) Problem(e) identifiziert." -ForegroundColor Gray
return $trustIssues
} catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
Write-Host " Keine Domain-Trusts vorhanden oder keine Berechtigung." -ForegroundColor Gray
return @()
} catch {
Write-Warning "Fehler beim Abfragen der Domain-Trusts: $($_.Exception.Message)"
return @()
}
}
# Hauptskript
function Start-ADSecurityAudit {
Write-Host "Active Directory Sicherheitsaudit gestartet..." -ForegroundColor Yellow
Write-Host "======================================================" -ForegroundColor Yellow
# Sammle alle Audit-Daten
$auditResults = @{
AdminAccounts = Test-AdminAccounts
StaleAccounts = Test-StaleAccounts
PasswordPolicy = if ($IncludePasswordPolicy) { Test-PasswordPolicy } else { $null }
ServiceAccounts = Test-ServiceAccounts
ComputerAccounts = Test-ComputerAccounts
GPOIssues = Test-GPOSecurity
TrustIssues = Test-DomainTrusts
Domain = Get-ADDomain
Forest = Get-ADForest
}
# Erstelle HTML-Bericht
$html = New-HTMLReport -Title "Active Directory Sicherheitsaudit"
# Zusammenfassung
$criticalIssues = 0
$warningIssues = 0
$criticalIssues += ($auditResults.AdminAccounts | Where-Object { $_.PasswordNeverExpires -eq $true }).Count
$criticalIssues += $auditResults.TrustIssues.Count
$warningIssues += $auditResults.StaleAccounts.Count
$warningIssues += $auditResults.ServiceAccounts.Count
$warningIssues += $auditResults.GPOIssues.Count
$summaryContent = @"
Audit-Zusammenfassung
Domain: $($auditResults.Domain.DNSRoot)
Kritische Probleme: $criticalIssues
Warnungen: $warningIssues
"@
$html += $summaryContent
# Administrator-Konten
$adminTable = $auditResults.AdminAccounts | ConvertTo-Html -Fragment
$adminRecommendations = Get-AdminAccountRecommendations -AdminAccounts $auditResults.AdminAccounts
$html += Add-HTMLSection -Title "Administrator-Konten" -Content "$adminTable$adminRecommendations
" -Type "info"
# Verwaiste Konten
if ($auditResults.StaleAccounts.Count -gt 0) {
$staleTable = $auditResults.StaleAccounts | ConvertTo-Html -Fragment
$staleRecommendations = Get-StaleAccountRecommendations -StaleAccounts $auditResults.StaleAccounts
$html += Add-HTMLSection -Title "Verwaiste Benutzerkonten (>90 Tage inaktiv)" -Content "$staleTable$staleRecommendations
" -Type "warning"
} else {
$staleRecommendations = Get-StaleAccountRecommendations -StaleAccounts @()
$html += Add-HTMLSection -Title "Verwaiste Benutzerkonten" -Content $staleRecommendations -Type "info"
}
# Service-Konten
if ($auditResults.ServiceAccounts.Count -gt 0) {
$serviceTable = $auditResults.ServiceAccounts | ConvertTo-Html -Fragment
$serviceRecommendations = Get-ServiceAccountRecommendations -ServiceAccounts $auditResults.ServiceAccounts
$html += Add-HTMLSection -Title "Vulnerable Service-Konten (Kerberoasting-Risiko)" -Content "$serviceTable$serviceRecommendations
" -Type "critical"
} else {
$serviceRecommendations = Get-ServiceAccountRecommendations -ServiceAccounts @()
$html += Add-HTMLSection -Title "Service-Konten" -Content $serviceRecommendations -Type "info"
}
# Computer-Konten
if ($auditResults.ComputerAccounts.Count -gt 0) {
$computerTable = $auditResults.ComputerAccounts | ConvertTo-Html -Fragment
$computerRecommendations = Get-ComputerAccountRecommendations -ComputerAccounts $auditResults.ComputerAccounts
$html += Add-HTMLSection -Title "Verwaiste Computer-Konten (>60 Tage inaktiv)" -Content "$computerTable$computerRecommendations
" -Type "warning"
} else {
$computerRecommendations = Get-ComputerAccountRecommendations -ComputerAccounts @()
$html += Add-HTMLSection -Title "Computer-Konten" -Content $computerRecommendations -Type "info"
}
# GPO-Probleme
if ($auditResults.GPOIssues.Count -gt 0) {
$gpoTable = $auditResults.GPOIssues | ConvertTo-Html -Fragment
$html += Add-HTMLSection -Title "Group Policy Probleme" -Content $gpoTable -Type "warning"
}
# Trust-Probleme
if ($auditResults.TrustIssues.Count -gt 0) {
$trustTable = $auditResults.TrustIssues | ConvertTo-Html -Fragment
$html += Add-HTMLSection -Title "Domain Trust Probleme" -Content $trustTable -Type "critical"
}
# Passwort-Richtlinien
if ($auditResults.PasswordPolicy) {
$policyContent = @"
Standard-Passwort-Richtlinie
Minimale Passwort-Länge: $($auditResults.PasswordPolicy.DefaultPolicy.MinPasswordLength)
Passwort-Verlauf: $($auditResults.PasswordPolicy.DefaultPolicy.PasswordHistoryCount)
Maximales Passwort-Alter: $($auditResults.PasswordPolicy.DefaultPolicy.MaxPasswordAge.Days) Tage
Minimales Passwort-Alter: $($auditResults.PasswordPolicy.DefaultPolicy.MinPasswordAge.Days) Tage
Komplexitäts-Anforderungen: $($auditResults.PasswordPolicy.DefaultPolicy.ComplexityEnabled)
"@
if ($auditResults.PasswordPolicy.WeakPolicyUsers.Count -gt 0) {
$weakUsersTable = $auditResults.PasswordPolicy.WeakPolicyUsers | Select-Object SamAccountName, Name, PasswordNeverExpires, PasswordNotRequired | ConvertTo-Html -Fragment
$policyContent += "Benutzer mit schwachen Passwort-Einstellungen $weakUsersTable"
$policyContent += @"
🔴 SOFORTMASSNAHME: Schwache Passwort-Einstellungen gefunden!
Aktion: Passwort-Richtlinien für diese Benutzer korrigieren
# Passwort-Ablauf aktivieren:
Set-ADUser -Identity "BENUTZERNAME" -PasswordNeverExpires `$false
# Passwort-Anforderung aktivieren:
Set-ADUser -Identity "BENUTZERNAME" -PasswordNotRequired `$false
"@
}
$html += Add-HTMLSection -Title "Passwort-Richtlinien" -Content $policyContent -Type "info"
}
# Allgemeine Sicherheitsempfehlungen
$generalRecommendations = Get-GeneralSecurityRecommendations
$html += Add-HTMLSection -Title "Allgemeine Sicherheitsempfehlungen" -Content $generalRecommendations -Type "recommendations"
$html += Close-HTMLReport
# Speichere Bericht
$html | Out-File -FilePath $OutputPath -Encoding UTF8
Write-Host "Bericht wurde erstellt: $OutputPath" -ForegroundColor Green
# Optional: Exportiere zu CSV
if ($ExportToCSV) {
$csvFolder = Split-Path $OutputPath -Parent
$baseFileName = [System.IO.Path]::GetFileNameWithoutExtension($OutputPath)
# Exportiere verschiedene Bereiche in separate CSV-Dateien
if ($auditResults.AdminAccounts.Count -gt 0) {
$csvPath = Join-Path $csvFolder "$($baseFileName)_AdminAccounts.csv"
$auditResults.AdminAccounts | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8 -Delimiter ";"
Write-Host "Admin-Konten exportiert nach: $csvPath" -ForegroundColor Green
}
if ($auditResults.StaleAccounts.Count -gt 0) {
$csvPath = Join-Path $csvFolder "$($baseFileName)_StaleAccounts.csv"
$auditResults.StaleAccounts | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8 -Delimiter ";"
Write-Host "Verwaiste Konten exportiert nach: $csvPath" -ForegroundColor Green
}
if ($auditResults.ServiceAccounts.Count -gt 0) {
$csvPath = Join-Path $csvFolder "$($baseFileName)_ServiceAccounts.csv"
$auditResults.ServiceAccounts | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8 -Delimiter ";"
Write-Host "Service-Konten exportiert nach: $csvPath" -ForegroundColor Green
}
if ($auditResults.ComputerAccounts.Count -gt 0) {
$csvPath = Join-Path $csvFolder "$($baseFileName)_ComputerAccounts.csv"
$auditResults.ComputerAccounts | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8 -Delimiter ";"
Write-Host "Computer-Konten exportiert nach: $csvPath" -ForegroundColor Green
}
}
return $auditResults
}
# Führe das Audit aus
try {
$results = Start-ADSecurityAudit
Write-Host "======================================================" -ForegroundColor Yellow
Write-Host "Active Directory Sicherheitsaudit abgeschlossen!" -ForegroundColor Green
Write-Host "Bericht: $OutputPath" -ForegroundColor Green
# Zeige Zusammenfassung in der Konsole
Write-Host "`nZusammenfassung:" -ForegroundColor Cyan
Write-Host "- Administrator-Konten: $($results.AdminAccounts.Count)" -ForegroundColor White
Write-Host "- Verwaiste Benutzerkonten: $($results.StaleAccounts.Count)" -ForegroundColor White
Write-Host "- Vulnerable Service-Konten: $($results.ServiceAccounts.Count)" -ForegroundColor White
Write-Host "- Verwaiste Computer: $($results.ComputerAccounts.Count)" -ForegroundColor White
Write-Host "- GPO-Probleme: $($results.GPOIssues.Count)" -ForegroundColor White
Write-Host "- Trust-Probleme: $($results.TrustIssues.Count)" -ForegroundColor White
} catch {
Write-Error "Fehler beim Ausführen des Audits: $($_.Exception.Message)"
}
# =================================================================
# NEUE ERWEITERUNG 1: LAPS (Local Admin Password Solution) Prüfung
# =================================================================
function Test-LAPSStatus {
Write-Host "🔒 Prüfe LAPS-Status..." -ForegroundColor Green
try {
# Prüfe LAPS Schema-Erweiterungen
$rootDSE = Get-ADRootDSE
$lapsSchema = Get-ADObject -SearchBase $rootDSE.schemaNamingContext -Filter "name -eq 'ms-Mcs-AdmPwd'" -ErrorAction SilentlyContinue
if ($lapsSchema) {
Write-Host " ✅ LAPS Schema-Erweiterung vorhanden" -ForegroundColor Green
# Prüfe Computer mit/ohne LAPS
$allComputers = Get-ADComputer -Filter "Enabled -eq 'true'" -Properties "ms-Mcs-AdmPwdExpirationTime", LastLogonDate
$computersWithLAPS = $allComputers | Where-Object { $_.("ms-Mcs-AdmPwdExpirationTime") -ne $null }
$computersWithoutLAPS = $allComputers | Where-Object { $_.("ms-Mcs-AdmPwdExpirationTime") -eq $null }
# Prüfe LAPS-Passwort-Alter
$expiredLAPS = $computersWithLAPS | Where-Object {
$expirationTime = [DateTime]::FromFileTime($_.("ms-Mcs-AdmPwdExpirationTime"))
$expirationTime -lt (Get-Date)
}
Write-Host " 📊 $($computersWithLAPS.Count) Computer mit LAPS, $($computersWithoutLAPS.Count) ohne LAPS" -ForegroundColor Gray
return @{
LAPSInstalled = $true
TotalComputers = $allComputers.Count
ComputersWithLAPS = $computersWithLAPS.Count
ComputersWithoutLAPS = $computersWithoutLAPS.Count
VulnerableComputers = $computersWithoutLAPS | Select-Object Name, LastLogonDate, OperatingSystem
ExpiredLAPS = $expiredLAPS.Count
ExpiredLAPSComputers = $expiredLAPS | Select-Object Name, @{Name='ExpirationDate'; Expression={[DateTime]::FromFileTime($_.("ms-Mcs-AdmPwdExpirationTime"))}}
}
} else {
Write-Host " ❌ LAPS Schema-Erweiterung nicht vorhanden" -ForegroundColor Red
return @{
LAPSInstalled = $false
Message = "LAPS ist nicht installiert - KRITISCHES SICHERHEITSRISIKO!"
TotalComputers = (Get-ADComputer -Filter "Enabled -eq 'true'").Count
}
}
} catch {
Write-Warning "LAPS-Prüfung fehlgeschlagen: $($_.Exception.Message)"
return @{ LAPSInstalled = $false; Error = $_.Exception.Message }
}
}
# =================================================================
# NEUE ERWEITERUNG 2: Advanced Kerberos Security Checks
# =================================================================
function Test-AdvancedKerberosSecurity {
Write-Host "🎯 Erweiterte Kerberos-Sicherheitsprüfung..." -ForegroundColor Green
$kerberosIssues = @()
try {
# 1. Unconstrained Delegation
Write-Host " 🔍 Prüfe Unconstrained Delegation..." -ForegroundColor Gray
$unconstrainedComputers = Get-ADComputer -Filter "TrustedForDelegation -eq 'true'" -Properties TrustedForDelegation -ErrorAction SilentlyContinue
foreach ($computer in $unconstrainedComputers) {
$kerberosIssues += [PSCustomObject]@{
Type = "Unconstrained Delegation"
Object = $computer.Name
ObjectType = "Computer"
Risk = "Critical"
Description = "Computer kann für jeden Service delegieren - Golden Ticket Risiko"
Recommendation = "Auf Constrained Delegation umstellen"
TechnicalDetails = "TrustedForDelegation = True"
}
}
# 2. Kerberoasting-anfällige Accounts
Write-Host " 🔍 Prüfe Kerberoasting-Risiken..." -ForegroundColor Gray
$spnUsers = Get-ADUser -Filter "ServicePrincipalNames -like '*'" -Properties ServicePrincipalNames, PasswordLastSet, PasswordNeverExpires -ErrorAction SilentlyContinue
foreach ($user in $spnUsers) {
$riskLevel = "Medium"
$issues = @()
if ($user.PasswordNeverExpires) {
$riskLevel = "Critical"
$issues += "Passwort läuft nie ab"
}
if ($user.PasswordLastSet -lt (Get-Date).AddDays(-365)) {
$riskLevel = "High"
$issues += "Passwort älter als 1 Jahr"
}
if ($user.PasswordLastSet -lt (Get-Date).AddDays(-730)) {
$riskLevel = "Critical"
$issues += "Passwort älter als 2 Jahre"
}
$kerberosIssues += [PSCustomObject]@{
Type = "Kerberoasting Risk"
Object = $user.SamAccountName
ObjectType = "User"
Risk = $riskLevel
Description = "SPN-Account anfällig für Kerberoasting: $($issues -join ', ')"
Recommendation = "Starkes Passwort (25+ Zeichen) oder Managed Service Account verwenden"
TechnicalDetails = "SPNs: $($user.ServicePrincipalNames -join '; ')"
}
}
# 3. AS-REP Roasting-anfällige Accounts
Write-Host " 🔍 Prüfe AS-REP Roasting-Risiken..." -ForegroundColor Gray
$asrepUsers = Get-ADUser -Filter "DoesNotRequirePreAuth -eq 'true'" -Properties DoesNotRequirePreAuth -ErrorAction SilentlyContinue
foreach ($user in $asrepUsers) {
$kerberosIssues += [PSCustomObject]@{
Type = "AS-REP Roasting"
Object = $user.SamAccountName
ObjectType = "User"
Risk = "High"
Description = "Kerberos Pre-Authentication deaktiviert - AS-REP Roasting möglich"
Recommendation = "Pre-Authentication aktivieren: Set-ADUser -Identity '$($user.SamAccountName)' -DoesNotRequirePreAuth \$false"
TechnicalDetails = "DoesNotRequirePreAuth = True"
}
}
# 4. Schwache Kerberos-Verschlüsselung
Write-Host " 🔍 Prüfe Kerberos-Verschlüsselung..." -ForegroundColor Gray
$allUsers = Get-ADUser -Filter * -Properties "msDS-SupportedEncryptionTypes" -ErrorAction SilentlyContinue
$weakEncryption = $allUsers | Where-Object {
$_.("msDS-SupportedEncryptionTypes") -band 0x01 -or # DES_CBC_CRC
$_.("msDS-SupportedEncryptionTypes") -band 0x02 # DES_CBC_MD5
}
foreach ($user in $weakEncryption) {
$encTypes = @()
if ($user.("msDS-SupportedEncryptionTypes") -band 0x01) { $encTypes += "DES_CBC_CRC" }
if ($user.("msDS-SupportedEncryptionTypes") -band 0x02) { $encTypes += "DES_CBC_MD5" }
$kerberosIssues += [PSCustomObject]@{
Type = "Weak Encryption"
Object = $user.SamAccountName
ObjectType = "User"
Risk = "Medium"
Description = "Schwache DES-Verschlüsselung aktiviert: $($encTypes -join ', ')"
Recommendation = "Nur AES-Verschlüsselung verwenden: Set-ADUser -KerberosEncryptionType AES128,AES256"
TechnicalDetails = "Schwache Verschlüsselung: $($encTypes -join ', ')"
}
}
# 5. Constrained Delegation mit Protocol Transition
Write-Host " 🔍 Prüfe Constrained Delegation..." -ForegroundColor Gray
$constrainedDelegation = Get-ADObject -Filter "msDS-AllowedToDelegateTo -like '*'" -Properties msDS-AllowedToDelegateTo, "msDS-AllowedToActOnBehalfOfOtherIdentity" -ErrorAction SilentlyContinue
foreach ($obj in $constrainedDelegation) {
$kerberosIssues += [PSCustomObject]@{
Type = "Constrained Delegation"
Object = $obj.Name
ObjectType = $obj.ObjectClass
Risk = "Medium"
Description = "Constrained Delegation konfiguriert - Überwachung erforderlich"
Recommendation = "Regelmäßige Überprüfung der Delegation-Einstellungen"
TechnicalDetails = "Delegiert an: $($obj.('msDS-AllowedToDelegateTo') -join '; ')"
}
}
Write-Host " 📊 $($kerberosIssues.Count) Kerberos-Sicherheitsprobleme identifiziert" -ForegroundColor Gray
return $kerberosIssues
} catch {
Write-Warning "Kerberos-Sicherheitsprüfung fehlgeschlagen: $($_.Exception.Message)"
return @()
}
}
# =================================================================
# NEUE ERWEITERUNG 3: E-Mail-Benachrichtigung bei kritischen Befunden
# =================================================================
function Send-CriticalAlert {
param(
[Parameter(Mandatory=$true)]$AuditResults,
[string]$SMTPServer = "smtp.example.com",
[string]$From = "ad-security@example.com",
[string]$To = "admin@example.com",
[string]$Subject = "🚨 KRITISCHE AD-SICHERHEITSPROBLEME GEFUNDEN"
)
if (-not $SendCriticalAlerts) {
Write-Host " 📧 E-Mail-Alerts deaktiviert" -ForegroundColor Gray
return $false
}
Write-Host "📧 Prüfe kritische Befunde für E-Mail-Alert..." -ForegroundColor Green
# Sammle kritische Befunde
$criticalIssues = @()
$criticalCount = 0
# Admin-Konten mit Problemen
$criticalAdmins = $AuditResults.AdminAccounts | Where-Object { $_.PasswordNeverExpires -eq $true }
if ($criticalAdmins.Count -gt 0) {
$criticalIssues += "🔴 $($criticalAdmins.Count) Admin-Konten mit 'Passwort läuft nie ab'"
$criticalCount += $criticalAdmins.Count
}
# LAPS nicht installiert
if ($AuditResults.LAPSStatus -and -not $AuditResults.LAPSStatus.LAPSInstalled) {
$criticalIssues += "🔴 LAPS nicht installiert - KRITISCHES RISIKO!"
$criticalCount += 1
}
# Computer ohne LAPS
if ($AuditResults.LAPSStatus -and $AuditResults.LAPSStatus.LAPSInstalled -and $AuditResults.LAPSStatus.ComputersWithoutLAPS -gt 0) {
$criticalIssues += "🔴 $($AuditResults.LAPSStatus.ComputersWithoutLAPS) Computer ohne LAPS-Schutz"
$criticalCount += $AuditResults.LAPSStatus.ComputersWithoutLAPS
}
# Kritische Kerberos-Probleme
$criticalKerberos = $AuditResults.KerberosIssues | Where-Object { $_.Risk -eq "Critical" }
if ($criticalKerberos.Count -gt 0) {
$criticalIssues += "🔴 $($criticalKerberos.Count) kritische Kerberos-Sicherheitsprobleme"
$criticalCount += $criticalKerberos.Count
}
# Vulnerable Service-Konten
if ($AuditResults.ServiceAccounts.Count -gt 0) {
$criticalIssues += "🔴 $($AuditResults.ServiceAccounts.Count) Kerberoasting-anfällige Service-Konten"
$criticalCount += $AuditResults.ServiceAccounts.Count
}
# Trust-Probleme
$criticalTrusts = $AuditResults.TrustIssues | Where-Object { $_.Severity -eq "Critical" }
if ($criticalTrusts.Count -gt 0) {
$criticalIssues += "🔴 $($criticalTrusts.Count) kritische Domain-Trust-Probleme"
$criticalCount += $criticalTrusts.Count
}
# Nur senden wenn kritische Probleme vorhanden
if ($criticalIssues.Count -gt 0) {
$currentDateTime = Get-Date -Format 'dd.MM.yyyy HH:mm:ss'
$issueList = $criticalIssues | ForEach-Object { "• $_" }
$staleCount = $AuditResults.StaleAccounts.Count + $AuditResults.ComputerAccounts.Count + $AuditResults.GPOIssues.Count
$body = @"
⚠️ KRITISCHE ACTIVE DIRECTORY SICHERHEITSPROBLEME ERKANNT!
SOFORTMASSNAHMEN ERFORDERLICH:
$($issueList -join "`n")
📊 ZUSAMMENFASSUNG:
• Domain: $($AuditResults.Domain.DNSRoot)
• Audit-Zeit: $currentDateTime
• Kritische Probleme: $criticalCount
• Warnungen: $staleCount
🎯 PRIORITÄTEN:
1. Admin-Konten mit "Passwort läuft nie ab" sofort korrigieren
2. LAPS installieren/konfigurieren (falls nicht vorhanden)
3. Kerberos-Sicherheitsprobleme beheben
4. Service-Account-Passwörter stärken
📋 NÄCHSTE SCHRITTE:
1. Vollständigen HTML-Bericht öffnen und prüfen
2. PowerShell-Befehle aus Empfehlungen ausführen
3. Änderungen dokumentieren
4. Erneutes Audit in 24h durchführen
⚡ AUTOMATISCHE BEFEHLE:
# Admin-Passwort-Ablauf aktivieren:
Set-ADUser -Identity "ADMIN_USERNAME" -PasswordNeverExpires \$false
# LAPS installieren (auf allen Computern):
msiexec /i LAPS.x64.msi /quiet
🛡️ Dieses Alert wurde automatisch generiert.
Bei Fragen kontaktieren Sie das IT Security Team.
---
AD Security Assessment Tool Enhanced v2.0
"@
try {
Send-MailMessage -SmtpServer $SMTPServer -From $From -To $To -Subject $Subject -Body $body -Priority High -ErrorAction Stop
Write-Host " 🚨 Kritische Befunde per E-Mail gesendet an: $To" -ForegroundColor Red
return $true
} catch {
Write-Warning "E-Mail-Versand fehlgeschlagen: $($_.Exception.Message)"
return $false
}
} else {
Write-Host " ✅ Keine kritischen Befunde - keine E-Mail erforderlich" -ForegroundColor Green
return $false
}
}
# Funktionen (erweitert)
function Get-AdminGroups {
$groupMappings = @{
"Domain-Admins" = @("Domain Admins", "Domänen-Admins", "Domäne-Admins")
"Enterprise-Admins" = @("Enterprise Admins", "Organisations-Admins", "Unternehmens-Admins")
"Schema-Admins" = @("Schema Admins", "Schema-Admins")
"Administrators" = @("Administrators", "Administratoren")
"Backup-Operators" = @("Backup Operators", "Sicherungs-Operatoren")
"Account-Operators" = @("Account Operators", "Konto-Operatoren")
"Server-Operators" = @("Server Operators", "Server-Operatoren")
"Print-Operators" = @("Print Operators", "Druck-Operatoren")
}
$foundGroups = @{}
foreach ($category in $groupMappings.Keys) {
foreach ($groupName in $groupMappings[$category]) {
try {
$group = Get-ADGroup -Identity $groupName -ErrorAction Stop
$foundGroups[$category] = $group.Name
Write-Verbose "Gefunden: $($group.Name) für Kategorie $category"
break
} catch {
continue
}
}
if (-not $foundGroups.ContainsKey($category)) {
Write-Verbose "Keine Gruppe für Kategorie $category gefunden"
}
}
return $foundGroups
}
function Test-AdminAccounts {
Write-Host "👑 Prüfe Administrator-Konten..." -ForegroundColor Green
$adminGroups = Get-AdminGroups
$results = @()
if ($adminGroups.Count -eq 0) {
Write-Warning "Keine Administrator-Gruppen gefunden. Prüfe Well-Known SIDs..."
$wellKnownGroups = @{
"Domain Admins" = "S-1-5-21-*-512"
"Enterprise Admins" = "S-1-5-21-*-519"
"Schema Admins" = "S-1-5-21-*-518"
"Administrators" = "S-1-5-32-544"
}
foreach ($groupType in $wellKnownGroups.Keys) {
try {
$domain = Get-ADDomain
$sidPattern = $wellKnownGroups[$groupType]
$sid = $sidPattern.Replace("*", $domain.DomainSID)
$group = Get-ADGroup -Identity $sid -ErrorAction Stop
$adminGroups[$groupType] = $group.Name
} catch {
Write-Verbose "Gruppe mit SID $sid nicht gefunden"
}
}
}
Write-Host " 📊 Gefundene Admin-Gruppen: $($adminGroups.Values -join ', ')" -ForegroundColor Cyan
foreach ($groupCategory in $adminGroups.Keys) {
$groupName = $adminGroups[$groupCategory]
try {
Write-Host " 🔍 Prüfe Gruppe: $groupName" -ForegroundColor Gray
$members = Get-ADGroupMember -Identity $groupName -Recursive -ErrorAction Stop
foreach ($member in $members) {
if ($member.objectClass -eq "user") {
$user = Get-ADUser -Identity $member.SamAccountName -Properties LastLogonDate, PasswordLastSet, Enabled, PasswordNeverExpires, PasswordNotRequired -ErrorAction Stop
# Erweiterte Risikobewertung
$riskFactors = @()
$riskScore = 0
if ($user.PasswordNeverExpires) { $riskFactors += "Passwort läuft nie ab"; $riskScore += 3 }
if ($user.PasswordNotRequired) { $riskFactors += "Kein Passwort erforderlich"; $riskScore += 3 }
if (-not $user.Enabled) { $riskFactors += "Konto deaktiviert"; $riskScore += 1 }
if ($user.LastLogonDate -and $user.LastLogonDate -lt (Get-Date).AddDays(-90)) { $riskFactors += "Lange nicht angemeldet"; $riskScore += 2 }
if ($user.PasswordLastSet -and $user.PasswordLastSet -lt (Get-Date).AddDays(-90)) { $riskFactors += "Altes Passwort"; $riskScore += 1 }
$riskLevel = switch ($riskScore) {
{ $_ -ge 6 } { "Kritisch" }
{ $_ -ge 3 } { "Hoch" }
{ $_ -ge 1 } { "Mittel" }
default { "Niedrig" }
}
$results += [PSCustomObject]@{
Group = $groupName
GroupCategory = $groupCategory
Username = $user.SamAccountName
DisplayName = $user.Name
Enabled = $user.Enabled
LastLogon = if ($user.LastLogonDate) { $user.LastLogonDate.ToString("dd.MM.yyyy") } else { "Nie" }
PasswordLastSet = if ($user.PasswordLastSet) { $user.PasswordLastSet.ToString("dd.MM.yyyy") } else { "Unbekannt" }
PasswordNeverExpires = $user.PasswordNeverExpires
PasswordNotRequired = $user.PasswordNotRequired
RiskLevel = $riskLevel
RiskFactors = $riskFactors -join ", "
RiskScore = $riskScore
}
}
}
} catch {
Write-Warning "Konnte Gruppe '$groupName' nicht abfragen: $($_.Exception.Message)"
}
}
if ($results.Count -eq 0) {
Write-Warning "Keine Administrator-Konten gefunden. Möglicherweise fehlen Berechtigungen."
} else {
Write-Host " 📊 $($results.Count) Administrator-Konten analysiert" -ForegroundColor Gray
}
return $results
}