Verteilte Erkennung von fehlgeschlagenen SSH-Loginversuchen

From
Revision as of 14:06, 11 October 2014 by Mattes (talk | contribs)
Jump to navigation Jump to search

Die derzeitige Situation

Auf den über SSH zugänglichen Rechnern (Gruenau- und Vogel-Rechner) laufen zwei verschiedene Betriebssysteme – OpenSUSE (Linux) und Solaris.

Die Behandlung fehlgeschlagener SSH-Loginversuche ist je nach Betriebssystem etwas unterschiedlich implementiert.

OpenSUSE

Fehlgeschlagene SSH-Logins werden unter OpenSUSE vom System in /var/log/btmp protokolliert. Ältere Einträge können sich auch in /var/log/btmp.* befinden. btmp ist üblicherweise nur für root les- und beschreibbar.

Das Shell-Skript /usr/bin/check-sshd wird in regelmäßigen Abständen von crond aufgerufen. Es listet alle btmp-Einträge mit Hilfe des Befehls lastb -a -i auf.

Ausschnitt aus einer Ausgabe von \emph{lastb -a -i} unter Linux (hier Ubuntu~14.01:

oliversc ssh:notty    Thu Oct  2 11:22 - 11:22  (00:00)     141.20.198.10
derp     ssh:notty    Thu Oct  2 11:17 - 11:17  (00:00)     141.20.193.124
maximili ssh:notty    Thu Oct  2 11:17 - 11:17  (00:00)     141.20.192.234
admin    ssh:notty    Wed Oct  1 15:43 - 15:43  (00:00)     61.174.50.225
bizz     ssh:notty    Wed Oct  1 15:32 - 15:32  (00:00)     127.0.0.1

lastb -a -i liefert einen btmp-Eintrag pro Zeile in Textform. Das erste Feld enthält den beim Login-Versuch verwendeten Benutzernamen, z.B. „derp“. Das zweite Feld gibt das Gerät an, von dem aus der Login vorgenommen wurde, im Falle von SSH-Logins „ssh:notty“. Die folgenden Felder geben das Datum und die Uhrzeit des Loginversuchs an. Das letzte Feld die IP-Adresse des entfernten Rechners. Die IP-Adresse kann als IPv4- oder IPv6-Adresse ausgegeben werden. Aus der Ausgabe von lastb filtert das Skript nur das letzte Feld, also die IP-Adresse, heraus.

Das Skript sucht nach IP-Adressen, die mindestens -mal direkt aufeinander folgen, wobei eine im Skript festgelegte Konstante ist. Diese IP-Adressen werden behalten, alle anderen verworfen.

Alle von iptables blockierten Adressen werden entfernt.

Danach werden Adressen der Netzwerke 141.20.0.0/16 (Humboldt-Universität) und 127.0.0.0/8 (Loopback), sowie weitere Adressen, die in /etc/hosts der Humboldt-Universität zugeordnet sind, aus der Liste entfernt.

Die übrigen Adressen werden über einen Aufruf von iptables auf unbegrenzte Zeit für SSH (TCP, Port 22) blockiert.

Solaris

Unter Solaris werden fehlgeschlagen SSH-Loginversuche vom System in der Datei /var/log/sshd.log protokolliert. Ältere Einträge können sich auch in /var/log/sshd.log.* befinden. Die Logdateien sind üblicherweise nur für root les- und beschreibbar.

Ausschnitt aus /var/log/sshd.log unter Solaris:

Aug  8 04:48:12 eule sshd[25199]: [ID 800047 local7.notice] Failed none for root from 141.20.198.10 port 1204 ssh2
Aug  8 04:48:12 eule sshd[25199]: [ID 800047 local7.notice] Failed password for root from 141.20.193.124 port 1204 ssh2
Aug  8 04:48:16 eule sshd[25199]: [ID 800047 local7.info] Disconnecting: Too many authentication failures for root
Aug  8 21:08:20 eule sshd[9925]: [ID 800047 local7.info] Illegal user admin from 141.20.192.234
Aug  8 21:08:20 eule sshd[9925]: [ID 800047 local7.info] Failed none for <invalid username> from 61.174.50.225 port 55581 ssh2
Aug  8 21:08:21 eule sshd[9925]: [ID 800047 local7.info] Failed keyboard-interactive for <invalid username> from 141.20.198.10 port 55581 ssh2

crond ruft regelmäßig das Shell-Skript /usr/sbin/check-sshd auf.

Das Skript filtert diejenigen Zeilen heraus, die die Zeichenketten „Failed keyboard-interactive“, „Illegal user“, „Failed password for <invalid“ oder „Failed password for“ enthalten. Da die Zeilen kein einheitliches Format besitzen, wird anhand dieser Zeichenketten entschieden, an welcher Position sich die IP-Adresse des entfernten Rechners befindet. Die Adresse wird extrahiert.

Das Skript sucht nach IP-Adressen, die mindestens -mal direkt aufeinander folgen, wobei eine im Skript festgelegte Konstante ist. Diese IP-Adressen werden behalten, alle anderen verworfen.

Wie unter OpenSUSE werden Adressen der Netzwerke 141.20.0.0/16 und 127.0.0.0/8 aus der Liste entfernt. Einträge in /etc/hosts werden allerdings nicht beachtet.

Alle von ippool blockierten Adressen werden entfernt.

Die restlichen Adressen werden mit Hilfe von ippool auf unbegrenzte Zeit blockiert.

Problemanalyse und Design

Erkennung von Angriffen

Da die Rechner derzeit nicht miteinander kommunizieren, werden Angriffe nur von einzelnen Rechnern erkannt. Die IP-Adresse des Angreifers wird lokal blockiert.

Local detection and blocking.png

Es wird gewünscht, dass ein auf einem Rechner erkannter Angriff dazu führt, dass die Angreiferadresse möglichst schnell auf allen Rechnern blockiert wird.

Distributed detection and blocking 1.png

Außerdem sollen aufeinanderfolgende, fehlgeschlagene Loginversuche an mehreren Rechnern, genauso als Angriff erkannt werden, als wären sie auf einem einzigen Rechner durchgeführt worden.

Distributed detection and blocking 2.png

Client-Server-Architektur

Beim Austausch von Daten über NFS würde der Komplexität der Netzwerkkommunikation von NFS übernommen. Die einzelnen Rechner könnten Daten über fehlgeschlagene Loginversuche in eigene Dateien schreiben. Die anderen Rechner könnten die Daten leicht auslesen. Wenn es möglich wäre, dass die Rechner über Änderungen an den Dateien informiert würden, z.B. über select oder inotify, könnten verteilte Angriffe schnell erkannt und blockiert werden. Ist das nicht möglich, müssen die Daten in regelmäßigen Abständen ausgetauscht werden, was einem Angreifer ein größeres Zeitfenster zwischen erstem Versuch und Blockieren lassen würde.

Eine rein verteilte Lösung ohne Server würde vermutlich eine komplexe Kommunikation zwischen den Rechnern erfordern. Jeder Rechner müsste alle anderen über Loginversuche informieren, was möglicherweise zu stärkerem Netzwerkverkehr führen würde, als bei den anderen Lösungen. Die Menge der beteiligten Rechner müsste verwaltet werden. Alle Rechner müssten für ihre Kommunikation einen bekannten Port öffnen.

Bei mehreren Clients und einem zentralen Server wäre die Kommunikation zwischen den Clients und dem zentralen Server etwas einfacher. Der Server könnte alle Daten über Loginversuche von den Clients annehmen, darin Angriffe erkennen und dann die Clients darüber informieren. Wenn die Clients die Verbindung zum Server aufbauen, bräuchten sie keinen eigenen Server-Port. Die Verbindung kann dauerhaft gehalten werden. Dadurch kennt der Server immer alle beteiligten Clients, die er informieren muss. Angriffe könnten unmittelbar erkannt und blockiert werden. Im Falle eines Serverausfalls könnten die Clients auf eine lokale Erkennung zurückfallen, so wie derzeit schon implementiert.

In Abwägung der Vor- und Nachteile der einzelnen Lösungen, haben wir uns für eine Client-Server-Lösung entschieden, wobei auch die NFS-Lösung sehr vielversprechend aussah. Da das Institutsnetzwerk sehr stabil ist, bestünde auch nur geringe Gefahr, dass der Server ausfiele und damit die verteilte Erkennung lahmlege.

Verlässlichkeitsforderungen

Es kann jederzeit passieren, dass ein Client ausfällt. Entweder durch einen Programmfehler, einen Neustart oder einen Ausfall des ganzen Rechners. Ebenso kann es zu einem Ausfall des Servers kommen. Wir wollen, dass alle Beteiligten mit Ausfällen zurecht kommen.

Diese Toleranz vereinfacht auch das Starten des Systems. Es soll keine Rolle spielen, in welcher Reihenfolge Clients und Server gestartet werden. Insbesondere sollen die Clients weiterlaufen, wenn einmal keine Verbindung zu dem Server möglich sein sollte. Unschön wäre, wenn sich alle Clients bei einem Server-Ausfall beenden würden.

Um diese Zuverlässigkeit zu ermöglichen, müssen wir

  • auf alle Fehler während der Socket-Kommunikation reagieren und abgebrochene Verbindungen automatisch wieder aufbauen,
  • bei jedem Verbindungsaufbau zwischen Client und Server alle notwendigen Daten austauschen, damit beide Seiten wieder den gemeinsamen Zustand kennen.

Bei einem Ausfall des Servers können die Clients nicht mehr über fehlgeschlagene Loginversuche informiert werden. In diesem Fall sollen sie Angreifer lokal sperren und Informationen darüber austauschen, sobald der Server wieder verfügbar ist.

Nachrichten

Client und Server tauschen drei Typen von Nachrichten aus.

FailedLogin-Nachrichten werden vom Client verschickt. Die Nachrichten enthalten eine eindeutige Nachrichten-ID und Daten über fehlgeschlagene SSH-Loginversuche, wie IP-Adresse des Rechners, bei dem der Login versucht wurde, die IP-Adresse des entfernten Rechners, von dem der Login ausging, der Login-Name, sowie Datum und Uhrzeit des Versuchs.

Ack-Nachrichten werden vom Server verschickt und benachrichtigen den Client darüber, dass alle mit der zugehörigen FailedLogin-Nachricht übertragenen Loginversuche persistent gespeichert wurden. Zu jede Ack-Nachricht ist über die Nachrichten-ID einer zuvor verschickten \texttt{FailedLogin}-Nachricht zugeordnet.

BlockIPs-Nachrichten werden vom Server verschickt. Sie enthalten eine Menge von IP-Adressen, die vom empfangenden Client blockiert werden sollen.

Behandlung fehlerhafter Nachrichten

Fehlerhafte Nachrichten führen dazu, dass die zugehörige Socket-Verbindung vom Empfänger abgebrochen wird.

Ack-Nachrichten, deren Nachrichten-ID dem Client unbekannt ist, können vom Client ignoriert werden, ohne die Verbindung abzubrechen.

Datenbank des Servers

Es reicht, eine persistente Speicherung aller Loginversuche aus FailedLogin-Nachrichten. Eine Ack-Nachricht darf erst gesendet werden, wenn Daten wirklich persistent gespeichert sind, also nach einem Server-Neustart wieder zur Verfügung stehen.

Der Algorithmus

Client und Server sind rein Ereignisgetrieben. Bis auf den Programmstart sind die Programme passiv.

Ereignisse sind Entweder fehlgeschlagene Loginversuche, die vom Betriebssystem des Clients kommen, oder über das Netzwerk eingehende Nachrichten, sowie Verbindungsaufbauten und -abbrüche zwischen Client und Server.