adPEAS v2 Episode 2: Unter der Haube - Anatomie eines Scans
Was passiert, wenn adPEAS ein Active Directory scannt? Von Authentifizierung und LDAP-Abfragen bis hin zu kontextabhängigen Severity-Bewertungen und Caching — ein Blick unter die Haube.
Was passiert eigentlich nach dem Enter?
In Episode 1 wurde gezeigt, wie adPEAS gestartet wird. Invoke-adPEAS -Domain contoso.com — Enter — und dann passiert eine ganze Menge. Aber was genau? In dieser Episode geht es darum, was unter der Haube abläuft, wenn adPEAS einen Scan durchführt.
Keine Sorge, das wird nicht zu technisch. Aber ein gewisses Verständnis der internen Abläufe hilft enorm beim Troubleshooting, wenn etwas nicht wie erwartet funktioniert. Und das wird passieren — Active Directory wäre nicht Active Directory, wenn nicht irgendwann etwas Unerwartetes auftaucht.
Phase 1: Die Verbindung
Alles beginnt mit der Authentifizierung. Je nach angegebenen Parametern wählt adPEAS den passenden Weg:
+--------------------------------------------------------------------+
| Connect-adPEAS |
| (Der Türsteher des Clubs) |
+---------------------------------+----------------------------------+
|
+--------------+-------------+-------------+--------------+
| | | | |
v v v v v
+----------+ +----------+ +-----------+ +------------+ +---------+
| Windows | | Kerberos | | NTLM | | Simple | | Ticket |
| Auth | | Native | | Imperson. | | Bind | | Import |
| (Current | | (TGT->TGS| | (runas | | (Username | | (Kirbi/ |
| User) | | ->PTT) | | /netonly) | | +Password) | | Ccache) |
+----------+ +----------+ +-----------+ +------------+ +---------+
Fallback bei Credentials: Kerberos -> NTLM Impersonation -> SimpleBind
Der häufigste Fall ist vermutlich Windows Auth — Anmeldung an der Domain mit dem aktuellen Account. Das ist simpel: adPEAS nutzt den Windows Credential Cache der Session.
Interessanter wird es bei expliziten Credentials. Dann versucht adPEAS zuerst native Kerberos:
- TGT holen — Ein Ticket Granting Ticket vom KDC anfordern
- TGS holen — Mit dem TGT ein Service Ticket für LDAP anfordern
- PTT — Das Ticket in die Windows Session importieren (Pass-the-Ticket)
- Verbinden — LDAP Connection aufbauen, Kerberos wird automatisch genutzt
Warum dieser Aufwand? Weil Kerberos in Enterprise-Umgebungen der Standard ist. Viele DCs haben SimpleBind deaktiviert oder erlauben es nur über LDAPS. Mit Kerberos ist man auf der sicheren Seite.
Falls Kerberos nicht funktioniert - Port 88 geblockt, DNS-Probleme, etc. - fällt adPEAS automatisch auf NTLM Impersonation zurück (unterstützt LDAP Signing). Falls auch das fehlschlägt, wird SimpleBind versucht. Es erscheint eine Warnung, aber der Scan läuft trotzdem. (Hinweis: PTT benötigt keine Admin-Rechte - adPEAS nutzt LsaConnectUntrusted.)
Phase 2: Das LDAPContext-Objekt
Sobald die Verbindung steht, sammelt adPEAS grundlegende Informationen über die Domain. Diese werden im $Script:LDAPContext gespeichert — dem Notizbuch, das adPEAS während des gesamten Scans verwendet:
$Script:LDAPContext = @{
Domain = "contoso.com"
DomainDN = "DC=contoso,DC=com"
DomainSID = "S-1-5-21-3623811015-..."
Server = "DC01.contoso.com"
DefaultNamingContext = "DC=contoso,DC=com"
ConfigurationDN = "CN=Configuration,DC=contoso,DC=com"
SchemaNamingContext = "CN=Schema,CN=Configuration,DC=contoso,DC=com"
RootDomainNamingContext = "DC=contoso,DC=com"
# ... und noch einiges mehr (Protocol, Port, AuthMethod, etc.)
}
Warum ist das wichtig? Weil diese Werte überall gebraucht werden. Wenn ein Check-Modul wissen will, welche SIDs zu “Domain Admins” gehören, muss es <DomainSID>-512 kennen. Für die Analyse von ACLs wird das Schema-DN für Attribut-GUIDs benötigt. Und so weiter.
Das LDAPContext-Objekt ist das Gedächtnis von adPEAS für die aktuelle Session. Einmal gefüllt, wird es von allen Modulen geteilt.
Phase 3: Die LDAP-Engine
Jetzt wird es technisch interessant. Im Herzen von adPEAS sitzt Invoke-LDAPSearch — eine einzige Funktion, durch die jede LDAP-Abfrage läuft. Egal ob User, Computer oder ACLs — alles geht durch diesen Kanal:
# So sieht ein typischer Aufruf aus (vereinfacht):
$result = Invoke-LDAPSearch -Filter "(objectClass=user)" -Properties "sAMAccountName","memberOf"
Warum eine zentrale Funktion statt direkter LDAP-Aufrufe im Code?
Error Handling an einer Stelle: Wenn die Connection abbricht oder ein Timeout passiert, muss das nur an einer Stelle behandelt werden. Jedes Modul profitiert automatisch davon.
Paging: LDAP hat ein Limit, wie viele Ergebnisse pro Anfrage zurückkommen. In großen Domains mit tausenden Objekten muss “gepagged” werden — also mehrere Anfragen gemacht und die Ergebnisse zusammengefügt werden. Invoke-LDAPSearch macht das automatisch.
Logging: Bei aktiviertem -Verbose wird jeder einzelne LDAP-Filter angezeigt, der abgesetzt wird. Sehr hilfreich zum Debuggen.
# Mit Verbose:
Invoke-adPEAS -Domain "contoso.com" -Verbose
# Output (gekürzt):
# VERBOSE: [Invoke-LDAPSearch] Filter: (objectClass=user)
# VERBOSE: [Invoke-LDAPSearch] Returned 1523 results
# VERBOSE: [Invoke-LDAPSearch] Filter: (objectClass=group)
# VERBOSE: [Invoke-LDAPSearch] Returned 342 results
Phase 4: Die Check-Module
Jetzt kommt der eigentliche Scan. adPEAS hat über 40 Check-Module, die nacheinander (oder in Gruppen) ausgeführt werden. Jedes Modul ist für einen bestimmten Aspekt der AD-Sicherheit zuständig.
Der Ablauf ist immer gleich:
+------------------------------------------------------------+
| Check-Modul |
| (z.B. Get-KerberoastableAccounts) |
+-----------------------------+------------------------------+
|
+---------------------+---------------------+
v v v
+------------+ +----------+ +------------+
| 1. Ensure | | 2. Query | | 3. Analyze |
| Connection | | Data | | & Return |
+------------+ +----------+ +------------+
Schritt 1: Ensure Connection
Jedes Check-Modul beginnt mit dem gleichen Code:
if (-not (Ensure-LDAPConnection @PSBoundParameters)) {
return $null
}
Das prüft, ob eine gültige Session existiert. Falls nicht, wird automatisch eine aufgebaut (wenn beim Einzelaufruf Credentials angegeben wurden) oder der Check wird übersprungen.
Schritt 2: Daten abfragen
Jetzt holt das Modul die benötigten Daten über die Get-Domain* Funktionen aus den Core-Modulen:
# Beispiel: Kerberoastable Accounts finden
$users = Get-DomainUser -LDAPFilter "(servicePrincipalName=*)"
Schritt 3: Analysieren und zurückgeben
Die gefundenen Daten werden analysiert und mit Severity-Informationen versehen:
foreach ($user in $users) {
# Severity bestimmen
$user | Add-Member -NotePropertyName '_adPEASObjectType' -NotePropertyValue 'Kerberoastable' -Force
# An Reporting-Pipeline übergeben
Show-Object -Object $user
}
Das Severity-System
Jedes Finding, das adPEAS entdeckt, bekommt eine Severity-Bewertung. Das klingt zunächst simpel, ist es aber nicht — die Bewertung ist kontextabhängig.
Die Basis-Kategorien:
| Severity | Symbol | Farbe | Bedeutung |
|---|---|---|---|
| Finding | [!] | Rot | Sicherheitsproblem, Handlungsbedarf |
| Hint | [+] | Gelb | Interessant, sollte geprüft werden |
| Note | [*] | Grün | Information, kein Risiko |
| Secure | [#] | Rot auf Gelb | Gute Konfiguration |
Das Besondere ist die kontextabhängige Bewertung. Beispiel “Password Never Expires”:
# Bei einem normalen User: Hint (gelb)
# Interessant, aber kein großes Risiko
# Bei einem Domain Admin: Finding (rot)
# Das ist ein echtes Risiko!
# Bei einem gMSA (Managed Service Account): Note (grün)
# Ist bei gMSAs normal und gewollt
Technisch funktioniert das über die Get-AttributeSeverity Funktion, die mehrere Faktoren prüft:
- Welches Attribut? — Manche Attribute sind grundsätzlich kritischer als andere
- Welcher Wert? — Manche Werte sind problematischer als andere
- Welcher Kontext? — Ist der Account privilegiert? Welche Gruppen? Welcher Typ?
# Vereinfachtes Beispiel:
function Get-AttributeSeverity {
param($Attribute, $Value, $Object)
# Prüfen ob der Account privilegiert ist
$isPrivileged = Test-IsPrivileged -Object $Object
if ($Attribute -eq 'PasswordNeverExpires' -and $Value -eq $true) {
if ($isPrivileged) {
return "Finding" # Rot für Domain Admins
}
return "Hint" # Gelb für normale User
}
# ... weitere Logik
}
SID-basierte Prüfungen
Ein wichtiger Aspekt, der nicht fehlen darf: adPEAS macht alle Berechtigungsprüfungen SID-basiert, nicht namensbasiert.
Warum? Weil Active Directory international eingesetzt wird. Die Gruppe “Domain Admins” heißt auf einem deutschen DC “Domänen-Admins” und in anderen Sprachen völlig anders. Namensbasierte Prüfungen würden auf lokalisierten Systemen fehlschlagen. Deshalb prüft adPEAS die SID:
# FALSCH - Funktioniert nur auf englischen Systemen:
if ($group.Name -eq "Domain Admins") { ... }
# RICHTIG - Funktioniert überall:
if ($group.SID -match "-512$") { ... } # Domain Admins haben immer SID *-512
Phase 5: Das Reporting
Am Ende eines Scans (oder parallel dazu) werden alle Findings gesammelt und aufbereitet. Intern nutzt adPEAS eine Unified RenderModel Pipeline: Wenn ein Check Show-Object $user aufruft, baut Get-RenderModel ein renderer-agnostisches Datenmodell - Severity-Klassifizierung, Attribut-Reihenfolge und Transformer-Logik werden einmal bestimmt. Daraus rendern Render-ConsoleObject (ANSI-Farben) und Render-HtmlObject (HTML mit Tooltips) jeweils ihre Ausgabe. Details dazu in Episode 5: Output & Reports.
Je nach gewählten Output-Optionen:
Console Output (Default)
Die Ergebnisse werden direkt in der Console angezeigt - farbcodiert und strukturiert. Ideal für schnelle Checks, wenn sofort ersichtlich sein soll, was Sache ist.
HTML Report
Über -Outputfile wird ein interaktiver HTML-Report generiert mit:
- Security Score (Gesamtbewertung)
- Finding Cards (aufklappbare Details)
- Tooltips mit Erklärungen und Remediation-Steps
- Such- und Filterfunktion
- Dark Mode
Das Caching-System
Das Caching verdient einen eigenen Abschnitt — allerdings ist wichtig zu verstehen, was adPEAS cached und was nicht.
Was NICHT gecached wird: LDAP-Query-Ergebnisse. Jeder Aufruf von Get-DomainUser, Get-DomainComputer oder Get-DomainGroup geht frisch an den DC. Wenn zwei verschiedene Check-Module beide Get-DomainUser -LDAPFilter "(servicePrincipalName=*)" aufrufen, werden tatsächlich zwei separate LDAP-Abfragen gemacht. Das ist gewollt — so arbeiten die Checks immer mit aktuellen Daten.
Was gecached wird: Die aufwändigen Nebenoperationen, die bei der Analyse anfallen. Allen voran die SID-zu-Namen-Auflösung. ConvertFrom-SID hat einen eigenen Cache:
$Script:SIDResolutionCache = @{
'S-1-5-21-3623811015-...-512' = 'CONTOSO\Domain Admins'
'S-1-5-21-3623811015-...-519' = 'CONTOSO\Enterprise Admins'
# ... wird während des Scans gefüllt
}
In großen Domains mit tausenden ACLs müsste adPEAS ohne Caching die gleiche SID hunderte Male auflösen. Mit Caching geschieht das einmal, das Ergebnis wird gespeichert.
Neben dem SID-Cache gibt es noch weitere:
- Global Catalog Connection (
$Script:GCConnection) — Wenn adPEAS bei Trust-Analysen oder ACL-Prüfungen auf SIDs aus fremden Domains im Forest trifft, bautConvertFrom-SIDautomatisch eine Verbindung zum Global Catalog auf (Port 3268 bzw. 3269 bei LDAPS). Diese Verbindung wird für die gesamte Session gecached und beiDisconnect-adPEASaufgeräumt. Falls der GC nicht erreichbar ist, fällt adPEAS auf ein RID-basiertes Format zurück (z.B.FOREIGN\DomainAdmins (RID:512)). - Privileged-Check-Cache — Merkt sich, ob eine SID zu einer privilegierten Gruppe gehört. Da die Privilegien-Prüfung rekursive Gruppenmitgliedschaften auflöst, spart das erheblich Abfragen.
- Group-Membership-Cache — Speichert rekursive Gruppenmitgliedschaften, die über den LDAP Matching Rule Filter aufgelöst wurden.
- DNS-Cache — Hostnames zu IP-Adressen, damit nicht bei jeder Operation DNS angefragt wird.
Das Resultat: Signifikant schnellere Scans, weniger Last auf dem DC — und trotzdem immer aktuelle Daten bei den eigentlichen LDAP-Abfragen.
Troubleshooting: Was tun wenn es klemmt?
Zum Abschluss noch einige Tipps, falls der Scan nicht wie erwartet läuft:
“No LDAP connection available”
Die Verbindung zum DC ist nicht möglich. Zu prüfen:
- Ist Port 389 (LDAP) oder 636 (LDAPS) erreichbar?
- Stimmt der Domainname?
- Sind die Credentials korrekt?
# Schnelltest:
Test-NetConnection dc01.contoso.com -Port 389
“The server is not operational”
Ein Klassiker. Der DC antwortet nicht oder die DNS-Auflösung funktioniert nicht.
# DNS prüfen:
Resolve-DnsName contoso.com
Resolve-DnsName _ldap._tcp.dc._msdcs.contoso.com -Type SRV
Kerberos funktioniert nicht
Wenn die native Kerberos-Auth nicht klappt, versucht adPEAS zuerst NTLM Impersonation. Falls auch das fehlschlägt, wird SimpleBind versucht. Mögliche Gründe für Fehler:
- Port 88 (Kerberos) geblockt
- Restriktive Sicherheitsrichtlinien (LSA-Zugriff eingeschränkt)
- DNS-Probleme bei der KDC-Auflösung
Mit -ForceSimpleBind lässt sich Kerberos und NTLM überspringen, mit -ForceNTLM wird NTLM Impersonation erzwungen:
# SimpleBind — Credentials direkt an den DC senden
Connect-adPEAS -Domain "contoso.com" -Credential $cred -ForceSimpleBind
# NTLM Impersonation — ähnlich wie "runas /netonly", behält bestehende Kerberos-Tickets
Connect-adPEAS -Domain "contoso.com" -Credential $cred -ForceNTLM
Der Scan ist langsam
Bei sehr großen Domains (100k+ Objekte) kann ein Scan dauern. Einige Maßnahmen:
- Nur bestimmte Module ausführen:
-Module Domain,Accounts - OPSEC-Modus (weniger Abfragen):
-OPSEC - Spezifischen DC angeben statt Auto-Discovery:
-Server DC01.contoso.com
Zusammenfassung
Hier nochmals die Phasen im Überblick:
- Verbindung — Authentifizierung und Session-Aufbau
- Kontext — Sammeln von Domain-Informationen
- Checks — Module führen ihre Analysen durch
- Severity — Kontextabhängige Bewertung der Findings
- Reporting — Aufbereitung und Ausgabe
Das Verständnis dieser Phasen hilft beim Troubleshooting und dabei, die Ergebnisse richtig einzuordnen.
In den nächsten Episoden geht es tief in die Authentifizierung. Wir schauen uns an, wie adPEAS Kerberos nativ implementiert, was Pass-the-Hash und Pass-the-Key bedeuten und wie die Authentifizierung mit Zertifikaten über PKINIT funktioniert.
← Episode 1: Einführung | Episode 3: Authentifizierung — coming soon
Schlagwörter
Über den Autor
Verwandte Artikel
adPEAS v2 Blog-Serie: Active Directory Sicherheitsanalyse mit adPEAS
Einführung in adPEAS v2 — eine komplette Neuentwicklung des PowerShell-basierten Active Directory Analyse-Tools mit nativem Kerberos-Support, null Abhängigkeiten und über 40 Security-Checks.