Anleitung: ssh2dyn ssh nur von einer IP mit dynanmischer IP erlauben

  • Intension: Ich habe aus Versehen meinen Linux vServer mal für 3 Monate mit zugelassenem Passwort-Login für ssh auf Port 22 laufen lassen. Unabsichtlicher Honeypot:rolleyes:. Ist ja leicht denkbar, was da los war. Ellenlange Auth-Logs mit Brute Force Angriffen von IP's aus Russland, China und sonst wo. Konsequenz war natürlich, den Server komplett neu aufzubauen, obwohl es keinen Hinweis auf ein erfolgreichen Bruth Force Attacke gab. Den Server habe ich dann mit fail2ban abgesichert, den ssh Port verlegt und den Passwortlogin deaktiviert. Die bessere Methode wäre natürlich, den Zugriff von außen nur von einer (meiner) IP zuzulassen, da der Angreifer ja selbst mit fail2ban zumindest die Information bekommt, dass dort ein Server ist, auf dem ein Port geöffnet ist. Leider habe ich hier nur eine dynamische IP.


    Erfahrung: Es gibt (fast) nichts, was sich nicht mit Python lösen lässt!


    Voraussetzung: Der Server ist ein Linux Server. Ich habe das auf einem Debian 9 stretch getestet, sollte aber auch auf anderen Linux-Distributionen funktionieren. Python wird benötigt, das Skript ist in 2.7 geschrieben. UFW Firewall oder iptables sind installiert .Es besteht ein Account bei einem DynDns-Anbieter. Ich empfehle:


    SPDYN die aktualisieren die IP innerhalb von Sekunden und es ist kostenlos und unbefristet.


    Problem: Dieser Abschnitt dient ausschließlich der Erklärung. Alles Notwendige erledigt das Skript.


    Am sichersten ist es natürlich, wenn der Server nur ssh-Anfragen von eurerClient-IP zulässt. Falls man eine feste IP hat, ist das ganze mit einem Einzeiler erledigt.

    (Ich gehe davon aus, das die default-policy in iptables in der Chain INPUT auf DROP gesetzt ist, aber, wie gesagt, nur Erklärung, das Skript macht auch das für euch)

    Der Einzeiler wäre für eine Feste Client Beispiel-IP 93.123.23.24 und dem Server-ssh Port 22:


    Code
    iptables -I INPUT -p tcp --src 93.123.23.24 --dport 22 -j ACCEPT

    Ab jetzt nimmt der Server, sofern keine andere Regel für Port 22 besteht, nur noch Anfragen für ssh von der Adresse 93.123.23.24 entgegen. Das ist save, solange ihr 'Eigentümer' der IP 93.123.23.24 seid.

    Dumm nur, wenn es sich um eine dynamisch vom Provider zugeteilte IP handelt. Dann hat man sich beim nächsten IP-Wechsel äußerst wirksam von seinem Server ausgesperrt!!!!

    Letzter Ausweg aus dieser Situation wäre dann das VNC-Popup im SCP unter 'Bildschirm'.


    Lösung: Wenn man direkten Zugriff auf den Server mit Bildschirm und Tastatur hätte, wäre die Lösung technisch einfach, aber arbeitsintensiv. Bei einen Wechsel der IP von z.B. 93.123.23.24 auf 93.123.105.36 müsste man mit:

    Code
    iptables -D INPUT -p tcp --src 93.123.23.24 --dport 22 -j ACCEPT

    die 'alte' Regel löschen und mit:

    Code
    iptables -I INPUT -p tcp --src 93.123.105.36 --dport 22 -j ACCEPT

    die 'neue' Regel setzen.

    Aber, das macht ja niemand, man kann seine Zeit sinnvoller verbringen;)

    Außerdem sitzt ihr ja nicht vor eurem Server, was das Ganze auch nicht leichter macht.


    Was macht also das Skript:

    Das Skript wird beim Booten des Servers über rc.local gestartet. Es läuft dann im Hintergrund. Als erstes stellt es für eine frei wählbare Zeit einen Fallback-Zustand her, in dem ihr euren Server auf jeden Fall über ssh erreichen könnt. Das funktioniert über die folgende vom Skript automatisch gesetzte iptables Rule(Bsp. Port 22, ist aber frei wählbar):

    Code
    iptables -I INPUT -p tcp --dport 22 -j ACCEPT

    Selbstverständlich heißt das nicht, das JEDER sich auf dem Server über ssh einloggen kann. Es gelten natürlich die von euch festgelegten Bedingungen(Passwort bzw. Zertifikat )
    Dieser Fallback dient dazu, dass man, falls mal was schief geht beim Testen mit dem Skript, wieder über ssh auf den Server zugreifen kann. Innerhalb der Fallback-Zeit könnt ihr dann über:

    Code
    ps -aux

    die pid des Skriptes ermitteln das Skript killen und zur Fehlersuche auf die vom Skript angelegten Log-Dateien zugreifen.

    Wird das Skript nicht gestoppt, legt es nach Ablauf der Fallback-Zeit wie folgt los:


    Als erstes wird die Fallback-Regel(Port frei wählbar):

    Code
    iptables -I INPUT -p tcp --dport 22 -j ACCEPT

    mittels:

    Code
    iptables -D INPUT -p tcp --dport 22 -j ACCEPT

    gelöscht.

    Jetzt ermittelt das Skript aus eurer Dyn-Adresse(frei wählbar) die IP z.B.93.123.23.24 und setzt die Regel:

    Code
    iptables -I INPUT -p tcp --src 93.123.23.34 --dport 22 -j ACCEPT

    Jetzt ist der ssh-Port eures Servers nur noch von eurer Client-IP erreichbar, für andere IP's nicht mehr.

    Hier mal ein Beispiel der Ausgabe von:

    Code
    iptables -t filter -L INPUT --line-numbers -n

    vor Ablauf der Fallback-Zeit(Auf meinem Server läuft ssh auf port 6666 siehe Zeile 3):

    Code
    1    f2b-openvpn  udp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 1194
    2    f2b-sshd     tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 6666
    3    ACCEPT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:6666
    4    ACCEPT       udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:1194
    ...

    und hier der gleiche Befehl nach Ablauf der Fallback-Zeit:

    Code
    Chain INPUT (policy DROP)
    num  target       prot opt source               destination         
    1    ACCEPT       tcp  --  77.22.33.151         0.0.0.0/0            tcp dpt:6666
    2    f2b-openvpn  udp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 1194
    3    ACCEPT       udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:1194
    ...

    Hier sieht man, das der Port 6666 jetzt nur noch von der Client IP 77.22.33.151 erreichbar ist. Die sicherste Konfiguration, die denkbar ist(mho).

    Andere Konfigurationen, wie der z.B. udp Port 1194 werden vom Skript nicht angefasst.

    Dem aufmerksamen Beobachter wird aufgefallen sein, dass vor der Fallback-Zeit der ssh Port noch durch fail2ban abgesichert war. das ist schon ziemlich sicher, aber liefert einem potenziellen Angreifer die Information, dass es einen offenen Port 6666 gibt. Nach dem Ablauf der Fallback-Zeit bekommt der potenzielle Angreifer aber höchstens die Information, dass der Port 6666 gedropt wird.(Hier dürfen sich gerne! mal die Netzwerkspezialisten in der Diskussion austoben, auf welchem Layer das passiert, und welche genaue Information der Angreifer bekommt. (Ist nicht mein Spezialgebiet, komme eher aus der Software-Ecke.). Logische Konsequenz ist es also, dass das Skript automatisch fail2ban für den ssh-Port abschaltet. Falls fail2ban bei euerem Server nicht läuft, spielt das keine Rolle.


    Sobald es zu einem IP-Wechsel auf der Client-Seite kommt, passt das im Hintergrund laufende Skript die iptables-rules entsprechend an. Bei dem oben empfohlenen dynip-Anbieter geht das innerhalb von Sekunden.


    Ok, das sollte erst einmal erklären, was ich mache.


    Stand heute ist, das Skript läuft auf meinem Server im Testbetrieb. Funktion 99 % Feinheiten 95%. Sobald beides auf 100% wird das hier public mit Anleitung.


    Meinungen über die Sinnhaftigkeit sind schon jetzt willkommen.


    Anleitung und Skript comming soon.

  • Ein bisschen paranoid für meinen Geschmack.

    Einen SSH Zugang mit PublicKey Auth noch zusätzlich abzusichern.


    PublicKey Auth bekommst du nicht überwunden, erst recht nicht mit einer BruteForce Attacke gegen Fail2Ban - dafür muss dir schon der Private Key von der Festplatte gestohlen werden, und das Passwort für den Key. Und wenn man soweit ist, kann man sich auch von deiner Quell IP auf dem Server anmelden.


    Warum ergreifst du so drastische Maßnahmen? Wo hat das einen Vorteil gegenüber Port-knocking oder SSH nur über VPN.


    Ich wüsste nicht, welchen Gewinn du hiermit erzielst.

  • Ja, natürlich ist das paranoid. Aber warum nicht! Das ist mho 'absolute Sicherheit'. Gut, dass du das durchschaut hast!!!! Das Passwort, was default von netcup erstellt wurde, hat mich laut auth-log ja schon vor der Bruteforce Attacke geschützt. Aber meine Paranoia lässt zu, dass ich kein Problem damit habe, aus Bequemlichkeit, root bei ssh zuzulsassen .

    Das ist ein Gewinn. oder?

    Danke für deine Meinung!

  • Aber meine Paranoia lässt zu, dass ich kein Problem damit habe, aus Bequemlichkeit, root bei ssh zuzulsassen .

    Das ist ein Gewinn. oder?

    Naja, wenn du deinen SSH Server auf einem Port > als 1024 hast, kann der sshd Prozess von jedem Nutzer gestartet werden.

    Insofern ist so ein High Port für mich nicht vertrauenswürdig - und ich würde mich hüten da irgendetwas einzugeben.


    Wer nutzt überhaupt noch unter systemd rc.local?


    Was war nochmal die letzte große Sicherheitslücke bei ssh? Tip: Debian

    Hast du mal eine CVE Nummer zur Hand?

  • Naja, wenn du deinen SSH Server auf einem Port > als 1024 hast, kann der sshd Prozess von jedem Nutzer gestartet werden.

    Insofern ist so ein High Port für mich nicht vertrauenswürdig - und ich würde mich hüten da irgendetwas einzugeben.

    Was nützt denn das mit:


    Chain INPUT (policy DROP)

    ACCEPT tcp -- 77.22.33.151 0.0.0.0/0 tcp dpt:6666

  • Naja, wenn du deinen SSH Server auf einem Port > als 1024 hast, kann der sshd Prozess von jedem Nutzer gestartet werden.

    Insofern ist so ein High Port für mich nicht vertrauenswürdig - und ich würde mich hüten da irgendetwas einzugeben.

    Nein, ich denke nur, von einem Nutzer mit meiner IP. Genau das will ich ja erreichen! Aber ich bin nicht unbelehrbar.

  • Code

    1. Chain INPUT (policy DROP)
    2. num target prot opt source destination
    3. 1 ACCEPT tcp -- 77.22.33.151 0.0.0.0/0 tcp dpt:6666
    4. 2 f2b-openvpn udp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 1194
    5. 3 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:1194
    6. ...
  • Bug in OpenSSL von 2008. Dafür bin ich zu jung.

    Wie kann man für Geschichte zu jung sein? Genau das ist ein sehr lehrreiches Stück im Bereich Engineering.


    vmk Gerne mehr Info, Wie gesagt, komme aus der Softwareecke...

    Ist denn das jetzt Paranoia oder mehr Sicherheit, was ich mache?


    Das kommt auf dein persönliches Kosten/Nutzenverhältnis an. Du bist mit deiner Lösung kein Stück gegen den Fall abgesichert, dass du dir einen Trojaner einfängst, der dir aus dem RAM SSH-Keys klaut und sich damit auf deinen Server einloggt. Hier könnte dir eine mehrstufige "Firewall" mit zweitem Faktor wie ein TOTP helfen.


    Mir persönlich reicht die Begrenzung der IP-Ranges sowie non-root und key only und ausgewählte Cipher. Allgemein matchen meine IP-Ranges auf meinen DSL-IP-Range sowie Arbeitgeber. Letzteres setzt natürlich vorraus, dass ich ein gewisses Vertrauen in meine Kollegen & Arbeigeber habe.

    "Security is like an onion - the more you dig in the more you want to cry"

  • Das war ein Debian-Problem, keins in OpenSSL oder SSH.

    Deine Aussage verstehe ich nicht. Willst du sagen, ssh sei nicht betroffen gewesen? Wo lag dann das Einfallstor?

    "Security is like an onion - the more you dig in the more you want to cry"

  • Deine Aussage verstehe ich nicht. Willst du sagen, ssh sei nicht betroffen gewesen? Wo lag dann das Einfallstor?

    das nicht genügend Zufallszahlen im Schlüssel waren, ein Debian Entwickler hat das damals wohl aus Performancegründen gemacht.


    ich werf mal

    sshsrc + mail versand oder andere sachen in den raum

  • Hier das Skript.

    Auf eigene Gefahr, ihr solltet wissen,was ihr hier macht!

    Anhang downloaden.

    .txt umbenennen in .py.

    In ein beliebiges Verzeichnis legen.

    Ausführbar machen.

    Im Skript die Zeilen zwischen ... # config # ... und .... # config end # ... anpassen

    In /etc/rc.local vor dem exit 0 diese zwei Zeilen einfügen (natürlich angepasst):

    iptables -I INPUT -p tcp --dport xx -j ACCEPT

    /path/to/where/you/put/it/ssh2dyn.py &


  • ... Du bist mit deiner Lösung kein Stück gegen den Fall abgesichert, dass du dir einen Trojaner einfängst, der dir aus dem RAM SSH-Keys klaut und sich damit auf deinen Server einloggt. ...

    Ok, das ist dann aber auf der Client Seite. Das Skript mach die Server Seite sicherer. Und wenn ein Trojaner in meinem RAM schnüffelt, hab ich ganz andere Probleme:):rolleyes::)

  • Das gilt ja wohl nur für Nutzer auf dem Server. Und da bin ich in diesem Fall der einzige Nutzer. Der Server ist mein ganz persönlicher VPN-Relay Server um das Vodafone ds-lite Dual Stack Problem zu umgehen. Bei einem Public-Server mit mehren Usern hast du natürlich Recht. Kommt halt auf den Anwendungsfall an. Aber das Skript funktioniert ja auch mit Port 22.


    Als Angreifer würde ich erst einmal die Standardports über eine IP-Range scannen. Erhalte ich dort eine Antwort, lohnt es sich, weiter zu bohren.


    Als nächstes arbeite ich daran die udp openvpn Ports automatisiert nur dann zu öffnen, wenn ein (zugelassener) Road Warrior darauf zugreift.

    Das ist aber komplexer, wegen der wechselnden IP. Der Vorteil liegt dann darin, das der Server bei einem Scan durch einen Angreifer die meiste Zeit so reagiert, als wäre er nicht vorhanden. Wo nichts ist, lohnt kein Angriff.


    und warum rc.local. Hey, ich will nur ein Python Skript, welches mit 0.0 Cpu-Last dümpelt auf einem vServer mit einem core starten. Wozu systemd?;)