#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

$Title

Generiert am: $(Get-Date -Format 'dd.MM.yyyy HH:mm:ss')

"@ } 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 += @"

Best Practices für Administrator-Konten:

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

Vorgehen bei verwaisten Konten:

  1. Schritt 1: Mit Vorgesetzten/HR klären ob Mitarbeiter noch im Unternehmen
  2. Schritt 2: Konto zunächst deaktivieren (nicht sofort löschen!)
  3. Schritt 3: Nach 30 Tagen Wartezeit endgültig löschen
  4. 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!

Sofortmaßnahmen:

  1. Passwörter sofort ändern (mindestens 25 Zeichen)
  2. Managed Service Accounts (MSA) implementieren wo möglich
  3. Minimale Berechtigungen vergeben
  4. 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

Vorgehen:

  1. Physische Existenz prüfen: Ist der Computer noch vorhanden?
  2. Netzwerk-Status: Ist der Computer noch im Netzwerk aktiv?
  3. 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 @"

Regelmäßige Sicherheitsmaßnahmen:

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

"@ 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 }