Skip to content

fail2ban fuer ssh fuer Arme

Wer einen Server in einschlägig bekannten Netzwerksegmenten (sprich: Bei den großen Housing-Anbietern) betreibt und wenigstens von Zeit zu Zeit mal in dessen Logs guckt kennt es: Irgendwelche Rechner aus Fernost oder anderswo werfen dem ssh-Daemon wild zusammengewürfelte Kombinationen aus Username und Passwort zu und tun dies mit einer bemerkenswerten Persistenz. Und wenn man auf dem System "fremde" User hat, lebt man als Admin stets mit der Angst, dass einer der Versuche der Angreifer irgendwann mal Erfolg hat.

Gegen diesen Typ der Brute-Force-Angriffe sind einige Wässerchen gewachsen, und ich möchte eine kleine Auswahl davon in diesem Artikel vorstellen.

Möglichkeit 1 ist die sauberste, aber leider nicht immer praktikable: Man schalte Password Authentication im ssh-Daemon ab und authentifiziere sich ausschließlich mit einem public/private-Key-Paar. Dann kommen die Cracker zwar immer noch am System an, man kann die Angriffe aber guten Gewissens ignorieren - sie haben eh keine Chance. Auf den meisten meiner Systeme ist der ssh-Login noch per Einschränkung des Keys (siehe man sshd, AUTHORIZED_KEYS FILE FORMAT, from="") und per tcp-wrapper (/etc/hosts.deny: sshd: ALL oder ALL: ALL) so zugenagelt, dass der Login nur von einigen meiner üblichen Aufenthaltsorte möglich ist - dank OpenVPN und/oder festen IP-Adressen kann man sich hier noch etwas verschanzen. Auf Systemen, die sowieso einen Paketfilter haben, ist auch im Paketfilter der Zugang zum ssh-Port auf die kurze Liste von IP-Adressen beschränkt.

Möglichkeit 2 ist security by obscurity: Man verschiebe den ssh-Daemon auf einen anderen Port. Die Cracker gehen davon aus, es mit ungepflegten Systemen in Standardkonfiguration zu tun zu haben und suchen nur auf tcp/22 nach dem sshd. Dann ist schlagartig Ruhe im Log; dafür hat man dann Schmerzen dort, wo schon jemand anders eine Freischaltung für tcp/22 durchgeboxt hat und man nun denselben Kampf mit dem Firewall-Admin für "seinen" Spezial-ssh-Port führen darf.

Möglichkeit 3 heißt fail2ban. Das ist ein in python (*würg*) geschriebener Userspace-Daemon, der in konfigurierbaren Logfiles nach konfigurierbaren Regexps sucht, die Logfiles auch beim Wachsen beobachtet und bei konfigurierbar gehäuftem Auftreten von auf diese Regexps matchenden Logeinträgen eine konfigurierbare Operation durchführt und diese nach konfigurierbarer Zeit wieder rückgängig macht. In der Defaultkonfiguration wird /var/log/auth.log auf "ROOT LOGIN REFUSED", "Authentication failure", "Failed foo for bar" und ähnliches gemonitored und im Falle des gehäuften Auftretens ein "iptables -I fail2ban-<name> 1 -s <ip> -j DROP" ausgeführt, d.h. alle weiteren Zugriffe von dieser IP-Adresse schon im Paketfilter verworfen. Das ist was feines, erzeugt keine Fehlalarme, braucht aber einen Userspace-Daemon in einer von mir ungeliebten Programmiersprache.

Da mir fail2ban nicht so wirklich zusagt, habe ich mir Möglichkeit 4 ausgedacht, die keine Zusatzsoftware benötigt, sondern mit dem in neueren Kerneln enthaltenen hashlimit match von iptables arbeitet und somit die ganze Geschichte direkt im Paketfilter abfackeln kann.

Hier ein Beispiel in der Notation von ferm:

def $SECONDS=600;
def $HITCOUNT=5;
def $LOGLIMIT="3/minute";
def $LOGBURST=10;
def $BLOCKSECONDS=600;

table filter {
  chain INPUT {
    mod state state NEW proto tcp dport 22 subchain {
      # all new tcp connections to port 22

      # insert into recent table
      mod recent
        name sshratelimit set NOP;

      # if excessive, log and drop
      mod recent
        name sshratelimit update seconds $SECONDS hitcount $HITCOUNT subchain {
          mod hashlimit
            hashlimit $LOGLIMIT hashlimit-burst $LOGBURST
            hashlimit-mode srcip hashlimit-name sshratelimit1
          LOG log-prefix "sshratelimit1 ";
          DROP;
      }
    }

    # if a host has been recorded for excessive ssh connects, drop and
    # log everything from there
    mod recent
      name sshratelimit rcheck seconds $BLOCKSECONDS hitcount $HITCOUNT subchain {
        mod hashlimit
          hashlimit $LOGLIMIT hashlimit-burst $LOGBURST
          hashlimit-mode srcip hashlimit-name sshratelimit2
        LOG log-prefix "sshratelimit2 ";
        DROP;
      }
  }
}
Dieser Code schickt erstmal alle Pakete, die eine neue TCP-Verbindung zu Port 22 aufmachen wollen, in eine Subchain. In dieser Subchain werden alle Pakete über den "recent" match in eine Hashtabelle aufgenommen und im nächsten Schritt jedes Paket, das über $HITCOUNT innerhalb $SECONDS eingeht, mit einem Limit zur Vermeidung von Log-DoS gelogged und dann ohne Limit verworfen. Der zweite Abschnitt sorgt dafür, dass jeglicher Traffic von einem Host, der es einmal in den Zustand "block" geschafft hat, ebenfalls limitiert gelogged und dann ebenfalls verworfen wird.

Im Log sieht das dann so aus:

     Apr 27 10:25:23 q ippl: port 22 connection attempt from 61.237.230.6 (61.237.230.6:52070->84.16.252.249:22)
     Apr 27 10:25:28 q sshd[20642]: (pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=61.237.230.6  user=root
     Apr 27 10:25:31 q sshd[20642]: Failed password for root from 61.237.230.6 port 52070 ssh2
     Apr 27 10:25:31 q ippl: port 22 connection attempt from 61.237.230.6 (61.237.230.6:52242->84.16.252.249:22)
     Apr 27 10:25:37 q sshd[20648]: (pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=61.237.230.6  user=root
     Apr 27 10:25:39 q sshd[20648]: Failed password for root from 61.237.230.6 port 52242 ssh2
     Apr 27 10:25:39 q ippl: port 22 connection attempt from 61.237.230.6 (61.237.230.6:52408->84.16.252.249:22)
     Apr 27 10:25:45 q sshd[20659]: (pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=61.237.230.6  user=root
     Apr 27 10:25:47 q sshd[20659]: Failed password for root from 61.237.230.6 port 52408 ssh2
     Apr 27 10:25:47 q ippl: port 22 connection attempt from 61.237.230.6 (61.237.230.6:52575->84.16.252.249:22)
     Apr 27 10:25:53 q sshd[20665]: (pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=61.237.230.6  user=root
     Apr 27 10:25:55 q sshd[20665]: Failed password for root from 61.237.230.6 port 52575 ssh2
     Apr 27 10:25:55 q kernel: sshratelimit1 IN=eth0 OUT= MAC=00:02:a5:79:45:3a:00:0c:db:eb:dc:40:08:00 SRC=61.237.230.6 DST=84.16.252.249 LEN=60 TOS=0x00 PREC=0x00 TTL=49 ID=58324 PROTO=TCP SPT=52749 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0
     Apr 27 10:25:56 q kernel: sshratelimit2 IN=eth0 OUT= MAC=00:02:a5:79:45:3a:00:0c:db:eb:dc:40:08:00 SRC=61.237.230.6 DST=84.16.252.249 LEN=52 TOS=0x00 PREC=0x20 TTL=49 ID=50655 PROTO=TCP SPT=52575 DPT=22 WINDOW=136 RES=0x00 ACK URGP=0
     Apr 27 10:25:57 q kernel: sshratelimit2 IN=eth0 OUT= MAC=00:02:a5:79:45:3a:00:0c:db:eb:dc:40:08:00 SRC=61.237.230.6 DST=84.16.252.249 LEN=52 TOS=0x00 PREC=0x20 TTL=49 ID=0 PROTO=TCP SPT=52575 DPT=22 WINDOW=136 RES=0x00 ACK URGP=0
     Apr 27 10:25:58 q kernel: sshratelimit1 IN=eth0 OUT= MAC=00:02:a5:79:45:3a:00:0c:db:eb:dc:40:08:00 SRC=61.237.230.6 DST=84.16.252.249 LEN=60 TOS=0x00 PREC=0x00 TTL=49 ID=58325 PROTO=TCP SPT=52749 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0
     Apr 27 10:26:00 q kernel: sshratelimit2 IN=eth0 OUT= MAC=00:02:a5:79:45:3a:00:0c:db:eb:dc:40:08:00 SRC=61.237.230.6 DST=84.16.252.249 LEN=52 TOS=0x00 PREC=0x20 TTL=49 ID=0 PROTO=TCP SPT=52575 DPT=22 WINDOW=136 RES=0x00 ACK URGP=0
     Apr 27 10:26:04 q kernel: sshratelimit1 IN=eth0 OUT= MAC=00:02:a5:79:45:3a:00:0c:db:eb:dc:40:08:00 SRC=61.237.230.6 DST=84.16.252.249 LEN=60 TOS=0x00 PREC=0x00 TTL=49 ID=58326 PROTO=TCP SPT=52749 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0
Man sieht hier sehr schön, wie der angreifende Zombie aus dem China Railway Telecommunications Center mit seinen Root-Login-Versuchen viermal am sshd selbst abprallt, bevor der Paketfilter zuerst mit sshratelimit1 verkündet, dass er jetzt zu viele ssh-Verbindungsversuche gesehen hat, und in Zukunft weitere Versuche mit sshratelimit2 ohne weitere Prüfung verwirft. Man sieht hier zwar nur ssh-Zugriffe, aber ein intelligenterer Angreifer könnte ja zeitgleich versuchen, andere Dienste auf dem Host anzugreifen.

Natürlich hat dieses Setup auch Nachteile: Es zählt jeden ssh-Verbindungsversuch - auch die erfolgreichen - und sperrt somit auch legitime User aus, die zu häufig connecten. Besonders auf Systemen, bei denen öfter mal Prozesse extern per ssh angestoßen werden, oder bei denen scp verwendet wird (das für jede Datei eine neue Verbindung öffnet), erzeugt man mit der hashlimit-Lösung schon mal ein false positive. Aber dafür kommt es ohne Userspace und vor allen Dingen ohne python aus.

Dieses Setup habe ich jetzt seit etwa einem halben Jahr auf einem meiner Mehrbenutzerrechner in Betrieb und habe noch keine Beschwerden gehört. Ich denke, es ist eine sparsame und brauchbare Alternative zu einer aufwendigeren Lösung wie fail2ban und werde es in den nächsten Wochen auf den restlichen Systemen ausrollen, die aufgrund ihrer Dienst- und Userstruktur nicht mit Security nach Möglichkeit 1 versorgt werden können.

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

Axel on :

Das Problem kommt mir irgendwie bekannt vor, aber ich kam bisher zu einem anderen Schluss:

fail2ban ist fuer mich ok auf allen Systemen, auf denen es viel RAM (mehr als 1-2GB) hat -- d.h. nicht auf alten Rechnern oder Embeddedsystemen. Denn was mich an fail2ban (und auch denyhosts) am meisten stoert, ist nicht Python (das ich zwar auch nicht mag, aber ok finde, sofern die damit geschriebene Software ordentlich funktioniert) sondern der fuer dieses Goodie viel zu hohen Speicherverbauch von um die 50 MB.

Apropos denyhosts: Das will man in keinster Weise verwenden, sobald man einmal in den Quellcode des Whitelist-Parsings geschaut hat: Es kann Wildcards in der Whitelist nur fuer /24er-Netze und wenn man das nutzt, dann legt es fuer jede IP darin intern eine Ausnahmeregel an. Das kommt sehr gut, wenn man zwei /16er-Netze whitelisten will (configfile mit 512 Zeilen per Skript generiert). Dann braucht denyhosts 'ne halbe Stunde oder so zum Generieren der internen Whitelist und RAM jenseits von gut und boese.

Die Loesung mit Anzahl Connects auf den ssh-Port kenne ich auch, ist mir aber ehrlich gesagt deutich zu risikoreich. (Und das, obwohl ich nicht wusste, dass scp fuer jede Datei eine neue TCP-Verbindung aufmacht.)

Dort, wo praktikabel habe ich den ssh-Port ebenfalls per iptables (ab und an auch per hosts.deny) auf wenige IPs beschraenkt. Ansonsten helfen auch noch Mindestanforderungen an die Passwoerter fuer guten Schlaf.

Die ideale Loesung habe ich aber auch noch nicht gefunden. Fuer mich waere das sowas wie fail2ban, nur mit deutlich weniger Speicherverbrauch -- gerne auch in was anderem als Python, Perl z.B. (Ein gut programmiertes fail2ban in C waere wahrscheinlich am sparsamsten in Sachen Speicher, haette schon wieder den faden Beigeschmack, den praktisch alle in C programmierte Software hat, die mit fremden Daten hantiert.)

P.S.: Das mit dem Favatar scheint zumindest im Preview nicht zu klappen, denn egal ob ich meine Homepage oder mein Blog angebe, es erscheint immer so ein generisches Gravatar-Dinges.

Andre on :

In C gibts sowas schon, nennt sich dann logsurfer und kann sogar noch mehr als nur iptables anpassen, da lassen sich IIRC freie Aktionen, je nach Kontext definieren :-)

Adi Kriegisch on :

Oder logfmon (http://logfmon.sourceforge.net/) -- ebenfalls in C, braucht wenig Speicher, kann beliebige Aktionen ausführen und ist einfach cool... ;-)

Stefan on :

Ich vermisse da irgendwie noch pam_abl und ähnliche PAM-Plugins. PAM sitzt naturgemäß näher an der Quelle als eine Software, die Logs parst, das je könnte. Gerade in Shared Hosting/User-Umgebungen ist es ja nicht nur die Angst vor den schlechten Paßwörtern anderer Benutzer, sondern auch die Unsicherheit evt. Apllikationen, die diese z.B. in PHP am Start haben. Und was ist, wenn da doch mal ein Log ist und sich plötzlich ein "logger -t ...." aufrufen läßt?

Marc 'Zugschlus' Haber on :

PAM ist mir ein Buch mit sieben Siegeln, da mach ich mich mal an einem ruhigen Winterabend dran.

Stefan Förster on :

Der Code ist von, ähm, "abwechslungsreicher" Qualität ;-)

Hans Bonfigt on :

Portknocking ?

Marc 'Zugschlus' Haber on :

Hab ich vor zwei Jahren mal eine Weile lang ausprobiert (siehe http://blog.zugschlus.de/archives/387-Port-Knocking-fuer-ssh.html) und wieder verworfen, weil

(a) die Benutzer sich anpassen müssen, das ist noch hässlicher als "ssh auf einem exotischen Port" (b) das Anklopfen selbst derzeit nicht befriedigend transparent in die ~/.ssh/config einzubauen ist - die einzige Möglichkeit ist den Klopfer als proxycommand zu definieren, und dabei muss dann der Klopfer über die ganze Session hinweg aktiv bleiben und jeglichen Traffic nochmal anfassen und (c) man hinter vielen FIrewalls mit den Klopfpattern nicht herauskommt und somit der eigene Server einen niemals reinlässt

meersau on :

Auf meinem Server habe ich mir die /etc/hosts.allow gesetzt.

piet on :

Hallo,

ich bin gerade beim testen von fail2ban. Da ich mich nicht besonders gut mit ip Filtern auskenne, war für mich fail2bin ein einfach zu konfigurierendes Programm.

Noch einfacher wäre natürlich der hier angebotene Code/Regel. Wie sieht es nun mit der Qualität dieser Regel aus.... ist ja schon ein Jahr her. Wie kann ich diesen Code in mein SuSE 11.2 mit Firewall einbinden, wenn überhaupt möglich. Warum performance mit fail2bin verschwenden wenn auch einfacher möglich.

Gruß piet

PS: kann ich beim eintreten einer Regel/Abfrage ein externes Programm/Script starten.

Marc 'Zugschlus' Haber on :

Piet schrieb:

Da ich mich nicht besonders gut mit ip Filtern >auskenne, war für mich fail2bin ein einfach zu >konfigurierendes Programm.

Dann solltest Du jemanden fragen, der sich mit der Thematik auskennt. IT-Sicherheit ist kein Spielplatz, hier kann man ohne Sachkenntnis mehr Schaden anrichten als einem lieb ist.

Noch einfacher wäre natürlich der hier angebotene >Code/Regel. Wie sieht es nun mit der Qualität dieser Regel aus.... >ist ja schon ein Jahr her.

Was meinst Du mit "Qualität"? Die Regel funktioniert, oder sie funktioniert nicht. Bei mir funktioniert sie, und ob Du sie in Deinen Paketfilter hinzunehmen kannst und ob Du mit ihren Nachteilen leben kannst, musst Du für Dich selbst entscheiden.

Wie kann ich diesen Code in mein SuSE 11.2 mit >Firewall einbinden, wenn überhaupt möglich.

Das müsstest Du jemanden fragen, der sich mit SuSE auskennt und auch gewillt ist, seinen Kopf dafür hinzuhalten.

Warum performance mit fail2bin verschwenden wenn >auch einfacher möglich.

Weil der Ansatz über hashlimit erstens relativ neu ist und zweitens auch die im Artikel erwähnten Nachteile hat.

Andreas Krey on :

Was ich mit diesen ssh-Lümmeln am liebsten machte, wäre kurzerhand bei allen Paketen von dieser Adresse Quell- und Zieladresse austauschen und sie wieder rauszuschicken. Sollen sie sich doch selbst hacken.

Add Comment

Markdown format allowed
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
Form options