Interessant ist es für viele Administratoren, das Anmeldeverhalten ihrer Nutzer nachverfolgen zu können. Besonders, wann und wie oft sich jemand mit einem falschen Passwort versucht hat anzumelden, kann dabei Aufschluss darüber geben, ob es sich wirklich um ein vergessenes Passwort handelt, oder ob jemand versucht, mittels Brute Force oder anderer Methoden auf das Active Directory zuzugreifen.
Index
Wenn zum Beispiel der Nutzer sich seit vier Tagen nicht mehr angemeldet hat, der letzte fehlgeschlagene Anmeldungsversuch allerdings weniger als vier Tage zurückliegen sollte, liegt die Vermutung nahe, dass hier etwas nicht stimmt und eventuell sogar ein Angriff vorliegt.
Doch wie liest man diese Daten richtig aus? Das ist leider gar nicht so einfach, wie man sich das vielleicht vorstellt. Der Grund dafür ist, dass diese Daten zwar vom Active Directory mitgeloggt werden, jedoch nicht zwischen den verschiedenen Domain Controllern in der Domäne repliziert werden.
Last Interactive Logon
Bevor es an das Skripten geht, möchte ich noch kurz auf eine Möglichkeit hinweisen, die das ganze etwas einfacher machen kann. Seit Windows Server 2008 bietet Microsoft die Option des „Last Interactive Logon“ an, um diese Anmeldeaktivitäten am Nutzer-Objekt zu loggen, die auch über mehrere DCs repliziert werden. Mehr Informationen dazu finden Sie in folgendem Technet-Artikel.
Allerdings gibt es da zwei Rahmenbedingungen, die Sie zu beachten haben: Zum einen muss Ihr Domain Functional Level mindestens auf Windows Server 2008 sein. Zum anderen werden bei der Anmeldung an einem PC in Ihrer Domäne Login Informationen über die letztens Logins angezeigt (siehe Screenshot unten). Die Funktion kann über Gruppenrichtlinien in Ihrer Domäne aktiviert werden.
badPwdCount und badPasswordTime
Wenn Sie das nicht wollen, gibt es aber auch eine Möglichkeit, ohne diese Funktion auszukommen. Logon-Informationen werden vom AD bereits geloggt, und zwar in den Attributen badPwdCount (Fehlgeschlagene Anmeldeversuche) und badPasswordTime (Zeitpunkt der letzten falschen Anmeldung). Das Problem ist, diese werden nur am jeweiligen DC an das Nutzerobjekt geschrieben, an dem der Nutzer versucht hat, sich anzumelden.
Trotzdem lässt sich das über PowerShell recht einfach zuverlässig auslesen. Nur können wir nicht einfach den Wert auslesen. Da die benötigten Attribute nicht repliziert werden, laufen wir in Gefahr, einen Wert von einem DC geliefert zu bekommen, der nicht aktuell ist. Daher müssen wir alle DCs in der Domäne auslesen und uns die richtigen Werte selbst heraussuchen.
Die Domain-Controller abfragen
Als erstes brauchen wir deswegen eine Liste mit allen DCs in der Domäne. Das geht ganz einfach mit dem entsprechenden CmdLet:
1 |
$dcs = Get-ADDomainController -Filter * | Select-Object name |
Anschließend initialisieren wir noch die Variablen, in denen wir die Anzahl der fehlgeschlagenen Anmeldungen sowie die Anmeldungszeit speichern.
1 2 3 |
$samAccountName = "test.user" $badpwdcount = 0 $badpwdtime = New-Object System.DateTime |
Diese Art der Initialisierung bei einem DateTime-Objekt garantiert uns, dass wir den geringstmöglichen Wert bekommen. Gibt man sich die $badpwdtime Variable aus, sieht man das auch. Diesen Wert brauchen wir gleich für Vergleiche mit dem Wert, den uns das AD beim Auslesen zurückliefern wird.
Jetzt gehen wir durch unsere ausgelesenen DCs und fragen jeden einzelnen davon nach unserem Nutzer ab, den wir überprüfen wollen. Das ist, wie erwähnt, notwendig, da die beiden Eigenschaften badPwdCount und badPasswordTime zwischen verschiedenen DCs unterschiedlich sein können, da diese nicht repliziert werden.
1 2 3 |
foreach($dc in $dcs) { $user = Get-ADUser $samAccountName -Server $dc.name -properties badPwdCount,badPasswordTime } |
Die richtigen Werte ausrechnen
Jetzt müssen wir vergleichen. Uns interessiert die Gesamtzahl der fehlgeschlagenen Logins auf allen unseren DCs. Die kann schließlich auf einem DC „0“ sein, während Sie auf dem nächsten vielleicht „1000“ beträgt – um einmal den Extremfall als Beispiel zu nehmen. Deswegen addieren wir einfach alle ausgelesenen Werte auf unsere $badpwdcount Variable, um am Ende die Gesamtzahl zu erhalten.
1 2 3 4 |
foreach($dc in $dcs) { $user = Get-ADUser $samAccountName -Server $dc.name -properties badPwdCount,badPasswordTime $badpwdcount += $user.badPwdCount } |
Bei der Zeit des letzten fehlgeschlagenen Logins interessiert uns natürlich die aktuellste. Deswegen vergleichen wir diese mit dem aktuellen Wert in unserer $badpwdtime Variable und wenn sie größer ist, überschreiben wir diese. Da wir die Variable mit dem geringstmöglichen Datum initialisiert haben, ist erst einmal jedes Datum, dass eventuell beim Nutzer in dem Attribut steht, größer. Durch das Überschreiben vergleichen wir dann beim nächsten Durchlauf mit unserem aktuell höchsten Datum, überschreiben dieses ggf. wieder und so weiter.
Eine kleine Hürde gibt es allerdings noch: Das AD liefert das Datum im Format Int64 (bzw. Long) zurück. Damit können wir natürlich im Vergleich nichts anfangen. Aber mit der fromFileTime-Funktion der DateTime-Klasse können wir den Wert ganz einfach in das aktuelle Zeitformat konvertieren und anschließend vergleichen.
1 2 3 4 5 6 7 8 9 |
foreach($dc in $dcs) { $user = Get-ADUser $samAccountName -Server $dc.name -properties badPwdCount,badPasswordTime $badpwdcount += $user.badPwdCount $userBadPwdTime = [datetime]::fromFileTime($user.badPasswordTime) if($badpwdtime -lt $userBadPwdTime) { $badpwdtime = $userBadPwdTime } } |
Die Ausgabe formatieren
Um den badPwdCount müssen wir uns nicht weiter kümmern – das ist eine einfache Ganzzahl, die wir problemlos loggen können. Das Datum soll allerdings noch in einem lesbaren Format herauskommen.
Dazu überprüfen wir erst einmal, ob überhaupt ein neues Datum gefunden wurde oder ob unsere Variable noch den initialen Wert enthält. Ist das nicht der Fall, können wir mit der ToString-Funktion und einem einfachen Format-String (in diesem Fall für das in Deutschland typische Datumsformat) das Datum formatiert ausgeben lassen.
1 2 3 4 5 |
if($badpwdtime -ne (New-Object System.DateTime)) { $bptString = $badpwdtime.ToString("dd.MM.yyyy HH:mm:ss") } else { $bptString = "-" } |
Und letztendlich lassen wir uns das Ergebnis ausgeben und schreiben es je nach Bedarf in ein Log:
1 |
Write-Host("User: " + $samAccountName + "- Failed logons: " + $badpwdcount + " - Last failed attempt: " + $bptString) |
Zusammenfassung & komplettes Skript
Zusammengefasst haben wir also folgendes gemacht:
- Liste aller DCs ausgeben lassen
- Denselben Nutzer von jedem DC abgerufen
- Den badPwdCount am jeweiligen DC ausgelesen und aufaddiert
- Die badPasswordTime ausgelesen und abgespeichert, wenn sie größer als die letzte ausgelesene Zeit war
Zum Abschluss noch einmal das komplette Skript zum Kopieren und Einfügen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$dcs = Get-ADDomainController -Filter * | Select-Object name $samAccountName = "test.user" $badpwdcount = 0 $badpwdtime = New-Object System.DateTime foreach($dc in $dcs) { $user = Get-ADUser $samAccountName -Server $dc.name -properties badPwdCount,badPasswordTime $badpwdcount += $user.badPwdCount $userBadPwdTime = [datetime]::fromFileTime($user.badPasswordTime) if($badpwdtime -lt $userBadPwdTime) { $badpwdtime = $userBadPwdTime } } if($badpwdtime -ne (New-Object System.DateTime)) { $bptString = $badpwdtime.ToString("dd.MM.yyyy HH:mm:ss") } else { $bptString = "-" } Write-Host("User: " + $samAccountName + "- Failed logons: " + $badpwdcount + " - Last failed attempt: " + $bptString) |
FirstAttribute AG – Ihr Microsoft Consulting Partner
Wir unterstützen Sie bei PowerShell Scripting Fragen.
Nehmen Sie Kontakt zu uns auf.
Leave a Reply
Danke für Ihre Anregungen, Fragen und Hinweise. Unsere Datenschutzerklärung finden Sie hier: https://www.active-directory-faq.de/datenschutzerklaerung/