HTTP-Server Algorithmus Verhindern des Ausbrechens aus docroot
Axel Richter
- webserver
Hallo,
Um für Schulungszwecke ServerSockets und Sockets zu demonstrieren und HTTP anzuwenden, erstelle ich gerade einen HTTP-Server mit Java. Natürlich möchte ich, obwohl der nie in den produktiven Einsatz gehen wird, auch Sicherheitsgedanken beachten. Folgendes Problem steht da zur Zeit an:
Wenn ein HTTP-Request eine Ressource anfordert:
GET http://host/abs_path
dann prüfe ich, ob das eine file-ressource im docroot ist, indem ich prüfe, ob das file(docroot + abs_path) existiert. Wenn ja, wird es ausgeliefert, wenn nein, dann wird je nach abs_path entweder ein 404 oder ein dynamisch generierter Response ausgeliefert.
Nun kann man ja allerdings
GET http://host/../../etc/file
anfordern. Angenommen, docroot wäre "/myHTTPS/wwwdocs/", dann würde das file("/myHTTPS/wwwdocs/../../etc/file") ja gefunden und ggf. ausgeliefert. Zugriffsberechtigungen und chroot-Umgebungen will ich nicht betrachten. Das Ganze soll einfach bleiben und auch unter Windows laufen.
Das Prüfen, ob "../" im abs_path vorkommt, würde helfen. Allerdings würden dann auch solche Anforderungen wie:
GET http://host/verzeichnis/../rootdoc.html
nicht mehr funktionieren. Nur zu prüfen, ob "../" am Beginn des abs_path steht, hilft auch nicht, da:
GET http://host/verzeichnis/../../../etc/file
immer noch möglich wäre.
Was ginge wäre, das File-Object zu erzeugen und dann zu prüfen, ob es im docroot liegt. Ist das die einzige Möglichkeit?
Gibt es einen allgemeingültigen Programmalgorithmus, womit man bei HTTP-Servern das Ausbrechen aus dem docroot verhinden kann, oder muss man wirklich zunächst die file-ressource referenzieren und dann vor dem Ausliefern prüfen, ob diese im docroot liegt?
viele Grüße
Axel
Moin,
Gibt es einen allgemeingültigen Programmalgorithmus, womit man bei HTTP-Servern das Ausbrechen aus dem docroot verhinden kann, oder muss man wirklich zunächst die file-ressource referenzieren und dann vor dem Ausliefern prüfen, ob diese im docroot liegt?
Die übliche Vorgehensweise ist erstmal aus dem Pfad einen kanonischen zu machen indem sämtliche Spielereien entfernt werden: alle ../ auflösen und alle ./ durch nichts ersetzen, ggbf. zwischendurch noch symbolische Links auflösen. Den Pfadnamen braucht man sowieso für allerlei Prüfungen (z.B. wenn für /foo besondere Rechte nötig sind und für /bar nicht, willst du nicht dass man die Rechteprüfung durch Benutzung von /bar/../foo umgehen kann) und da kann man dann auch trivial sehen in welchem Verzeichnis er liegt.
Hallo,
Die übliche Vorgehensweise ist erstmal aus dem Pfad einen kanonischen zu machen indem sämtliche Spielereien entfernt werden: alle ../ auflösen und alle ./ durch nichts ersetzen, ggbf. zwischendurch noch symbolische Links auflösen.
Ja, dazu wäre es dann aber auch nötig, zunächst das File(docroot + abs_path) zu referenzieren, um dann mit File.getCanonicalPath() den Pfad zu bestimmen. Jedenfalls wäre es so mit Java am einfachsten. Dann könnte man die Wurzel mit docroot vergleichen, um bei Nichtübereinstimmung das File zu verwerfen.
Den Pfadnamen braucht man sowieso für allerlei Prüfungen (z.B. wenn für /foo besondere Rechte nötig sind und für /bar nicht, willst du nicht dass man die Rechteprüfung durch Benutzung von /bar/../foo umgehen kann) und da kann man dann auch trivial sehen in welchem Verzeichnis er liegt.
Ja, das wäre eine Begründung für das Tun oben ;-)). Ein kompletter HTTP-Server sollte es allerdings nicht werden. Die gibt es, auch open source, auch schon zur Genüge ;-); die brauchte ich nur zu verwenden. Meiner soll _sehr_ übersichtlich sein und _nur_ zum Demonstrieren von Sockets und HTTP dienen.
Wahrscheinlich werde ich das Sicherheitsloch einfach drin lassen und nur kommentieren. Es ist ja auch interessant zu wissen, was man alles falsch machen kann.
viele Grüße
Axel
Hallo,
Die übliche Vorgehensweise ist erstmal aus dem Pfad einen kanonischen zu machen indem sämtliche Spielereien entfernt werden: alle ../ auflösen und alle ./ durch nichts ersetzen, ggbf. zwischendurch noch symbolische Links auflösen.
Eigentlich wollte ich mit meinem kleinen Programm _nur_ auf GET inklusive query-Teil reagieren.
Das ist auch fertig.
Das Ganze läuft aber nun mit Java so easy, dass ich Deinen Vorschlag doch eingebaut habe und nun auch alle anderen HTTP-Methoden inklusive HTTP-Auth und Cookies implementieren will. der Sinn ist, zu demonstrieren, was _in_ einem HTTP-Server wirklich _prinzipiell_ abläuft und wie die Clients HTTP umsetzen, speziell, wie die Requests aussehen, die ein Client sendet, wie unterschiedliche encodings von Formulardaten aussehen, wie HTTP-Auth mit realm umgesetzt wird, wie Cookies vom Server und vom Client gesendet und behandelt werden ...
Vielen Dank für die Anregung.
viele Grüße
Axel