Exchange: IP nach fehlgeschlagenen Logins sperren

Ein Leser wünschte sich einen Artikel, wie Brute Force Angriffe auf Exchange Server verhindert werden können. Da Exchange Server in kleineren Umgebungen häufig direkt im Internet erreichbar sind (beispielsweise per Portforward) und Dank Autodiscover auch sehr schnell ermittelt werden können, eignen sich Exchange Server sehr gut für Brute Force Angriffe. Bei einem Brute Force Angriff versucht der Angreifer das richtige Passwort zu erraten, dabei wird eine große Anzahl fehlgeschlagener Logins erzeugt. Im wesentlichen gibt es zwei Wege, wie sich Brute Force Angriffe verhindern lassen. Zum einen lässt sich der Benutzeraccount nach einer gewissen Anzahl falscher Logins automatisch sperren. Dieses Feature wird direkt durch das Active Directory unterstützt. Das Sperren des Benutzers steht allerdings in der Kritik, denn so lassen sich durch einen Brute Force Angriff unter Umständen viele Benutzer sperren, was dazu führt das die Benutzer nicht mehr arbeiten können. Zwar errät der Angreifer dadurch zwar nicht das Passwort, jedoch werden sich schnell Benutzer beschweren, wenn das Konto alle paar Minuten gesperrt ist.

Ein anderer Weg ist, die IP Adresse des Angreifers nach einer bestimmten Anzahl fehlgeschlagener Logins zu sperren. Das Benutzerkonto wird somit nicht gesperrt, sondern nur der Angreifer daran gehindert das Passwort zu erraten. In diesem Artikel stelle ich zwei Wege vor, wie eine IP mit zu vielen fehlgeschlagenen Logins gesperrt werden kann.

IP mit Windows Firewall und PowerShell sperren

Eine einfache Möglichkeit eine IP Adresse nach zu vielen fehlgeschlagenen Logins zu sperren, ist ein kleines PowerShell Script, welches die IP Adresse an der Windows Firewall sperrt. Die fehlgeschlagenen Logins werden am Exchange Server im Security Eventlog mit der ID 4625 protokolliert. In diesem Event findet sich neben dem Benutzernamen, auch die IP Adresse von dem der fehlgeschlagene Login ausgeführt wurde:

Event Log

Mit einem kleinen PowerShell Script lässt sich mit Hilfe der Aufgabenplanung alle 5 Minuten das Eventlog auf das Event 4625 prüfen. Tauchen im Log zu viele fehlgeschlagene Logins von der gleichen IP auf, wird eine Windows Firewall Regel erstellt, welche die IP blockiert:

Windows Firewall

Ich habe dazu ein einfaches PowerShell Script erstellt, welches die IP aus dem Event 4625 nach 5 fehlgeschlagenen Anmeldeversuchen mit einer Firewall Regel sperrt:

#Block IP after X failed Logins
$BlockCount = 5
#Get Date and Time
$Timestamp = get-date
#Get IPs vom Eventlog (last 10 min)
$Events = get-winevent -FilterHashtable @{Logname="Security";ID=4625;StartTime=[datetime]::Today.AddMinutes(-10)} -ea 0
#Exit Script if no Events found
if (!$Events) { exit 0 }
#Get IPs from Events
$IPs = @()
foreach ($Event in $Events) {
[xml]$XMLEvent = $Event.ToXml() 
$IPAddress = ($XMLEvent.Event.EventData.Data | where {$_.name -eq "IpAddress"} | select "#text").'#text'
$IPs += $IPAddress
}
#Group Events
if ($IPs.count -gt 1) {
$IPAddressCounts = $IPs | group | select count,name | where {$_.Count -ge $BlockCount}
}
#Block IPs
if ($IPAddressCounts.count -gt 1) {
foreach ($IPAddress in $IPAddressCounts) {
$IP = $IPAddress.Name
$FirewallRuleName = "BlockIP:" + $IP
$CheckFirewallRule = Get-NetFirewallRule -DisplayName $FirewallRuleName -ea 0
if ($CheckFirewallRule) {
#If FW Rule exists, update with current TimeStamp
$FirewallRule = Set-NetFirewallRule -DisplayName $FirewallRuleName -Description $Timestamp
} else {
#Create new FW Rule
$FirewallRule = New-NetFirewallRule -DisplayName "$FirewallRuleName" -Direction Inbound -Protocol TCP -Action Block -RemoteAddress $IP -Description $Timestamp	
}
}
}

Das Script kann alle 5 Minuten mit der Windows Aufgabenplanung ausgeführt werden (mit höchsten Privilegien). Wer möchte, kann die IP nach einer gewissen Zeit auch wieder entsperren indem die Firewall Regel beispielsweise nach 30 Minuten wieder gelöscht wird. Dazu kann dieses kleine Script genutzt werden:

#Unblock IP (Delete Firewall Rule) after X Minutes
$BlockTime = 30
#Get Date and Time
$Timestamp = get-date
$UnblockTimeStamp = $Timestamp.AddMinutes(-$BlockTime)
#Get all Firewall Rules
$FirewallRules = Get-NetFirewallRule -DisplayName "BlockIP:*"
#Unblock IPs / Delete Firewall Rules
foreach ($FirewallRule in $FirewallRules) {
$FirewallRuleDescription = $FirewallRule.Description
[datetime]$FirewallRuleCreatedTimeStamp = $FirewallRuleDescription
if ($FirewallRuleCreatedTimeStamp -lt $UnblockTimeStamp) {
$FirewallRuleName = $FirewallRule.DisplayName
Remove-NetFirewallRule -DisplayName $FirewallRuleName
}
}

Das erste Script schreibt den Zeitpunkt des Erstellens der Firewall Regel in den Kommentar der Regel, das zweite Script wertet den Zeitpunkt aus und löscht die Regel nach einer wählbaren Zeit wieder.

Die beiden Scripte lassen sich einfach an die eigenen Bedürfnisse anpassen, beispielsweise könnte man sich eine E-Mail schicken lassen, wenn eine IP gesperrt wurde. Die beiden Scripte haben aber auch ihre Nachteile, wenn das Script nur alle 5 Minuten läuft, wird eine IP möglicherweise auch erst nach 5 Minuten gesperrt. Brute Force Angriffe dauern in der Regel länger als 5 Minuten

RdpGuard (kostenpflichtig)

Eine andere Möglichkeit ist die Software RdpGuard, welche mit knapp 80 USD für eine Lizenz noch im preislichen Rahmen liegt. RdpGuard verfolgt einen ähnlichen Ansatz wie die beiden Scripte oben, reagiert also auch auf das Event 4625, kann aber zusätzlich noch weitere Logs auswerten und die IPs entsprechend blockieren. Somit lassen sich beispielsweise auch fehlgeschlagene Logins für SMTP, POP3 und SQL Server blockieren:

RdpGuard

Auf der Website wirbt die Software mit dem Slogan „Intrusion prevention system for your Windows Server“, dass ist bei dem Leistungsumfang vielleicht „etwas“ übertrieben, denn in beiden Fällen handelt es sich nur um eine Art „fail2ban“ welches man unter Linux für besonders für SSH Verbindungen kennt. Eine Demo von RdpGuard gibt es hier:

Fazit

Eine IP nach fehlgeschlagenen Logins zu sperren ist relativ einfach, funktioniert aber nur, wenn kein Reverse Proxy vor dem Exchange Server eingesetzt wird. Wenn ein Reverse Proxy zum Einsatz kommt, steht in den Eventlogs nur die IP des Proxys, nicht aber die originale Quell IP. Für kleine Umgebungen eignen sich diese beiden Möglichkeiten daher meiner Meinung nach gut um Brute Force Versuche zu blockieren. Für Umgebungen mit Reverse Proxy, muss diese Funktion am Proxy umgesetzt werden, dazu muss der Proxy allerdings auch die Authentifizierung durchführen.

19 Gedanken zu „Exchange: IP nach fehlgeschlagenen Logins sperren“

  1. Hallo Franky,

    super Artikel!
    Folgt den noch ein Artikel, wie man die Authentifizierung von Exchange zugriffen auf den Reverse Proxy verlegt? Um so zum Beispiel auch die anderen Seiten (RPC , ActiveSync…) gegen Angriffe zu schützen ?

    Antworten
  2. Kann das script nicht einfach mit der eventid 4625 getriggert werden?

    Habe eine sophos utm als waf laufen, in der eventid steht daher nur die interne ip der waf, gibt es hier auch eine lösung, die original ip zu ermitteln und zu sperren?

    Schöner wäre ein mfa, das auch ohne azure Hybrid laufen würde. Für webmail wäre das ja möglich, für die anderen exchange Dienste funktioniert dies leider nicht….

    Antworten
    • Suche mal nach Exchange HMA, das geht für alle Exchange Dienste, theoretisch auch ganz ohne Kosten.
      Ja, du brauchst eine Hybrid, kannst aber alles vom Exchange damit absichern (MFA).

      Antworten
  3. Für solche Fälle verwende ich syspeace (www.syspeace.com)
    Macht genau das. Man kann die Anzahl fehlerhafter Logins in einem bestimmten Zeitraum einstellen, sowie auch wie lange die IP-Adresse gesperrt werden soll. Zusätzlich können Country Rules definiert werden, so dass nur Logins aus bestimmt Ländern zugelassen werden. syspeace unterhält auch eine globale Blocklist von bekannten IP-Adressen, die an bei anderen Installationen negativ aufgefallen sind.

    Antworten
  4. Guten Morgen zusammen,

    das ist ja ein Klasse Script. Ich habe versucht mir das zu erweitern, damit es auf die EreignisID 20271 im System Protokpll reagiert.
    Leider ist die Meldung dort anders aufgebaut, so dass ich die IP nicht implementiert bekomme.
    Der Select $Events = get-winevent -FilterHashtable @{Logname=“System“;ID=20271;StartTime=[datetime]::Today.AddMinutes(-10)} -ea 0
    liefert:

    TimeCreated Id LevelDisplayName Message
    ———– — —————- ——-
    07.04.2022 05:47:38 20271 Warnung CoID={NA}: Der Benutzer 777, der eine Verbindung mit 91.191.209.234 hergestellt hat, konnte sich aus folgendem Grund nicht authentifizi…

    Das xml sieht so aus:


    {NA}
    777
    91.191.209.234
    Die Remoteverbindung wurde verweigert, weil die angegebene Kombination aus Benutzername und Kennwort nicht erkannt wird oder das ausgewählte Authentifizierungsprotokoll nicht für den RAS-Server zulässig ist.
    0x10
    B3020000

    Kann da jemand helfen?

    Vielen Dank im vorraus.

    Antworten
  5. Könnte das Script Exchange Reporter vielleicht eine Auswertung liefern, wieviele fehlerhafte Logins für welchen user durchgeführt wurde.

    Antworten
  6. Schöne Idee, aber macht das in IPv6 Zeiten noch Sinn? Anyway, ich habe es erstmal umgesetzt. Bisher werden die Accounts nur nach 25 fehlerhaften Versuchen gesperrt.

    Antworten
  7. nur blöd dass das bei Proxy Betrieb einfach nicht klappt … und welcher Exchange Profi stellt seinen Server nackt ans Internet? hoffentlich keiner …

    Antworten
  8. Hallo Frank,

    die Abfrage der Events liefert Ergebnisse des gesamten Tages.
    get-winevent -FilterHashtable @{Logname=“Security“;ID=4625;StartTime=[datetime]::Today.AddMinutes(-10)} -ea 0

    Um nur die letzten 10 Minuten zu bekommen, vielleicht so:
    $time = (Get-Date).AddMinutes(-30)
    $Events = get-winevent -FilterHashtable @{Logname=“Security“;ID=4625;StartTime=$time} -ea 0

    Eine einfache Ausschlussliste haben wir uns noch gewünscht:

    foreach ($Event in $Events) {
    [xml]$XMLEvent = $Event.ToXml()
    $IPAddress = ($XMLEvent.Event.EventData.Data | where {$_.name -eq „IpAddress“} | select „#text“).’#text‘
    #declare Ausschlussliste
    [string] $IPLIST = ‚192.168.1.23‘, ‚192.168.2.34‘, ‚192.168.3.45‘, ‚192.168.4.56‘
    if ($IPLIST.contains($IPAddress))
    {
    #do nothing
    }
    else
    {
    #add IP to Array
    $IPs += $IPAddress
    }

    }

    Vielen Dank für das Script und die Idee! Läuft einwandfrei.

    Antworten
  9. Hallo Frank,

    ich bin leider recht unbedarft was ShellCoding angeht und nun auf dein Script gestoßen.
    Die Frage die sich mir stellt ist; wie kann ich das Script testen? Ich sehe Login-Versuche im Eventlog mit gleichen IP-Adressen in kurzen Abständen (Teilweise Sekunden, scheint ein BruteForce zu sein, da die Usernames jeweils andere sind) – wenn ich das Script im Powershell ISE laufen lasse, steigt zwar die CPU merklich an, eine Regel wird jedoch nicht erstellt.

    Danke im Voraus! :)

    Antworten

Schreibe einen Kommentar