Sicherer Ad-hoc Server mit Python 3

Vor zwei Wochen veröffentlichte ich einen kurzen Blogartikel zum Thema Ad-hoc Server mit Python als Beispiel. any fragte daraufhin, ob es das ganze auch mit TLS/HTTPS und Authentifizierung gibt. Ja, das gibt es – sogar mit Bordmitteln – allerdings muss man hierzu ein wenig nachhelfen. Für diesen Blogartikel setze ich auf Python 3.

Vorbereitungen

Um den Webserver HTTPS-fähig zu machen, werden ein Zertifikat und ein Private Key benötigt.

Das ist nur ein kleines Beispiel, so ein Zertifikat zu erzeugen. Wie sich ganze CAs erstellen lassen, zeigt Thomas Leister in seinem Blog.

Grundlagen des HTTPS-Servers

Hierbei arbeiten wir nun nicht mehr mit Kommandozeilenbefehlen, sondern mit einem eigenständigen Skript.

Vereinfacht erklärt wird der Webserver in Zeile 7 auf dem Port 4443 initalisiert. Der SimpleHTTPRequestHandler verarbeitet alle Aufrufe. In Zeile 9 wird die HTTPS-Unterstützung eingebaut. Abschließend wird der HTTPServer aktiviert.

Authentifizierung

Die eigentliche Schwierigkeit ist es, jetzt noch eine Authentifizierung einzubauen. In jedem Fall müssen wir einen eigenen Handler entwickeln, um selber vorzugeben, wie sich der Server bei Anfragen verhalten soll. Eine einfache Implementierung für die Authentifizierung ist HTTP BasicAuthentication. Hier muss für den Aufruf (GET) des geschützten Bereiches das Headerfeld „Authorization“ mit gültigen Daten mitgeschickt werden. Das sieht bspw. so aus:

Der String hinter Basic ist Base64-codiert und würde im Beispiel viktor:password ergeben. Das ist eine Verkettung aus Benutzernamen und Passwort, getrennt durch einen Doppelpunkt. Wie wir allerdings sehen, sind die Daten zwar codiert, aber nicht chiffriert und somit abhörbar. In diesem Beispiel läuft die Verschlüsselung bereits eine Schicht tiefer über TLS ab, weshalb dieses Problem nicht so schwerwiegend ist. Trotzdem warnt die OWASP-Community vor Basic Auth, da neben unzureichender Verschlüsselung auf der HTTP-Ebene u.a. eine benutzerfreundliche Abmeldung nicht umsetzbar ist. (Zum Abmelden muss üblicherweise der Browser geschlossen werden)

LESETIPP  Solved: Error 403 in Munin

Das Ergebnis

So sieht der Server nach der Implementierung von Basic Authentication aus. Was ist nun passiert?

LESETIPP  Standardgateway unter Linux ermitteln

Die Authentifzierung

Neu ist die Klasse MyHandler. Sie ist der Handler für die Requests (dt. Anfragen). Mittels Vererbung, einem grundlegenden Konzept aus der objektorientierten Programmierung, greifen wir auf den bekannten SimpleHTTPRequestHandler zurück, der bisher die Abwicklung der Anfragen, die Auflistung des Verzeichnisses und den Download der Dateien übernommen hat. Lediglich die relevanten Funktionen für Basic Auth. müssen nur noch ausgetauscht werden. Das beschränkt sich auf do_GET() und do_HEAD(). Bei einer Anfrage von einem Browser wird diese in einer bedingten Verzweigung (Zeilen 19-29) auf den Authentication-Header geprüft. Es können nun drei Szenarien eintreten:

  1. Zeile 19: Ein Authorization-Header mit gültigen Anmeldedaten wurde vom Browser in der Anfrage übermittelt. Nun übernimmt die do_GET()-Funktion aus der Basisklasse und stellt die gewohnte Funktionalität (Verzeichnisanzeige, Download) bereit.
  2. Zeile 22: Es wurde kein Authorization-Header mitgeschickt. do_HEAD(authenticated=False) wird aufgerufen.
    Die Antwort auf die Anfrage wird den HTTP-Statuscode 401 (Unauthorized) tragen, im Feld WWW-Authenticate wird dem Browser übermittelt, dass Basic Auth. erwartet wird und der geschützte Bereich „Secure Share“ heißt (Zeilen 13 und 14).
    Würde der Nutzer die Anmeldung abbrechen, würde im Browser der Text „Anmeldung fehlgeschlagen“ stehen.
  3. Zeile 26: Es wurde kein gültiger Authorization-Header übermittelt. Dieser Fall tritt ein, wenn der Nutzer falsche Anmeldedaten eingegeben hat.
    In meinem Beispiel wird genau so wie bei einer ersten Anmeldung verfahren, der Nutzer erhält erneut ein Anmeldedialog. Man könnte auch schreiben, dass die Anmeldung ungültig war, allerdings bietet das für Angreifer möglicherweise Spielraum.

Fall 2 und 3 könnte man auch zusammenfassen, zur Anschaulichkeit und späteren Erweiterbarkeit habe ich diese allerdings getrennt.

Weitere Verbesserungen

Der Rest des Skripts ist eine Verschönerung des ersten Entwurfs. Nur wenn das Skript direkt ausgeführt und nicht importiert wird (Z. 53), werden die Befehle aus der main() ausgeführt.

LESETIPP  Debian 9 Stretch verspätet sich

Das Skript muss zwangläufig mit Argumenten aufgerufen werden. Hierunter fallen der Benutzername und das Kennwort, der Pfad der Zertifikatsdateien sowie optional ein benutzerdefinierter Port (Standard bleibt jedoch 4443). Im Beispiel müssen Zertifikat und Key in einem Verzeichnis liegen und cert.pem sowie key.pem heißen. Das ist natürlich nicht immer wünschenswert und nur zur Einfachheit so gelöst, da das Hauptaugenmerk auf der Anfragenabwicklung liegt. Weiterhin sollten Zertifikat und Schlüssel außerhalb des Working Directory liegen, da sie sonst heruntergeladen werden können, was wohl nicht im Sinne des Admins liegt.

Wichtig für den Aufruf des Skripts: das zur Verfügung gestellte Verzeichnis bleibt im Beispiel das Working Directory!

Zusammenfassung

Es ist also möglich, einen Webserver mit HTTPS (TLS) und einer einfachen Authentifizierung aufzusetzen. Ich hoffe, ich konnte ebenfalls einen kleinen Einblick in die Arbeit eines Webservers geben und möchte allerdings darauf hinweisen, dass ich nur an der Oberfläche gekratzt habe, da dieses Thema sehr vielfältig ist und den Bogen sonst überspannen würde.

Weitere Hinweise

Der GitHub-Nutzer „fxsjy“ hat in einem Gist einen ähnlichen Server zur Verfügung gestellt, der allerdings auf Python 2 setzt.

Trotz sorgfältigen Tests (Umgebung: Python 3.6, CPython) lassen sich Fehler nicht ausschließen, insbesondere bei unterschiedlichen Python-Versionen. Deshalb übernehme ich keine Haftung für Schäden. Zertifikatsdateien und Private-Keys sind mit höchster Vorsicht zu behandeln!

Auch ich stelle unter dem Repository vnotes-scripts dieses Skript unter der MIT-Lizenz zur Verfügung. Ich freue mich auf Feedback!

Gefällt dir der Artikel? Dann empfiehl ihn weiter!

9 Gedanken zu „Sicherer Ad-hoc Server mit Python 3“

  1. Ich bin nur grob drüber geflogen was mir aber sofort aufgefallen ist ist das „key“ als globale Variable verwendet? Wäre ein Instanzvariable hier nicht um einiges passender?

    1. Sicherlich ist diese Variante kein sauberes OOP, aber wesentlich anschaulicher als eine Instanzvariable.

      RequestHandlerClass: The user-provided request handler class; an instance of this class is created for each request. (https://docs.python.org/3/library/socketserver.html#socketserver.BaseServer)

      Die Handlerklasse wird demnach mit jedem Aufruf neu instanziiert. Da eine setter-Methode einzubauen und dieses jedes Mal aufzurufen würde unnötig komplex werden.
      Jedes Mal aus der Klasse auf sys.argv zurückzugreifen schafft weiterhin unnötig Probleme, sollte die Klasse importiert werden – dann haben wir zwar eine Instanzvariable, aber die Klasse lässt sich ähnlich schwer einbinden.

      Eine aus meiner Sicht durchaus bessere Idee wäre es, für die Authentifzierung eine Methode auth_user(self, username, password) zu implementieren, welche die Anmeldung mithilfe einer externen Datenquelle überprüfen und nebenbei Mehrbenutzerfähigkeit schaffen kann.
      Der Einfachheit und Anschaulichkeit halber habe ich aber darauf im Artikel verzichtet. Im Git-Repository kann das aber sicherlich in Zukunft noch verbessert werden.

      Gruß
      Viktor

  2. Hallo sieht sehr gut aus, aber das Script funktioniert bei mir leider nicht.
    Gestartet habe ich es so:
    „python3 /home/python/adhocserver.py test123 test123 /home/python 4443“
    cert.pem und key.pem liegen im Verzeichnis /home/python
    Und diese Fehlermeldung erhalte ich:

    Traceback (most recent call last):
    File „/home/python/adhocserver.py“, line 54, in
    main()
    File „/home/python/adhocserver.py“, line 49, in main
    httpd.socket = ssl.wrap_socket (httpd.socket, keyfile=KEYFILE, certfile=CERTFILE, server_side=True)
    File „/usr/lib/python3.2/ssl.py“, line 521, in wrap_socket
    ciphers=ciphers)
    File „/usr/lib/python3.2/ssl.py“, line 221, in __init__
    self.context.load_cert_chain(certfile, keyfile)
    IOError: [Errno 2] No such file or directory

    Kannst du mir vielleicht sagen was nicht richtig ist?
    Danke

    1. Hallo Joachim,

      sicher, dass die Zertifikatsdateien in /home/python liegen?

      No such file or directory

      Das deutet meist darauf hin, dass eine Datei nicht gefunden wurde.

      [ -f /home/python/cert.pem ] && [ -f /home/python/key.pem ] && echo "Alles ok" || echo "Eine der Dateien fehlt"

      Das Kommando oben würde im Terminal einen kleinen Schnellcheck durchführen.

      Grüße
      Viktor

  3. Hi Viktor, ja die Dateien liegen im /home/python.
    Ich habe dein Kommando ausgeführt und da stand „Alles ok“. 😉

    Aber die Fehlermeldung beim Ausführen des python scripts bleibt die gleiche.

    Gruß

    Joachim

    1. python3 /home/python/adhocserver.py test123 test123 /home/python/ 4443

      So sollte das Kommando richtig lauten. Der Schrägstrich hinter dem Cert-Pfad fehlt. Hab auch gleich mal das Script nachgebessert, sodass der Fehler in dem Zusammenhang nicht mehr auftreten sollte.
      Vielen Dank für das Feedback!

      Grüße
      Viktor

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.