IT Security in Web Anwendungen IV – HTTP(S): Zustandsbehaftet oder Zustandslos?

Doğan Uçar
Server

In dem vierten Teil der Blogserie „IT Security in Web Anwendungen“ thematisieren wir das HTTP(S) Übertragungsprotokoll, den Zustand von HTTP(S)-Anfragen sowie Erweiterungen zu dem Ursprungsentwurf und einen Blick aus Sicht der IT-Sicherheit. Anhand konkreter theoretischer Beispiele wollen wir die wichtigsten Punkte im Bezug auf IT-Security im HTTP(S)-Umfeld aufzeigen und Best Practices auflisten.

In unserem ersten Post dieser Blogserie sind wir das Thema „Injections“ angegangen und haben gezeigt, wie diese zustandekommen bzw. zu verhindern sind. In dem zweiten Teil der Serie sind wir auf  File Up-/Download und Storage eingegangen und haben allgemeine Sicherheitsrisiken sowie Prävention und Alternativen aufgelistet. In dem dritten Teil thematisieren wir Sessions und Session Hijacking-Attacken sowie einen möglichen Schutz gegen solcher Attacken.

Alle Posts dieser Blogserie sind unabhängig voneinander und es wird nicht vorausgesetzt, vorherige Posts zu kennen. Zur besseren Übersicht über das Thema „IT-Security“ empfehlen wir jedoch bei Post #1 anzufangen. In dem Kontext dieses Blogposts empfiehlt sich insbesondere #3, da Sessions (und Session Cookies) einen direkten Bezug auf den HTTP(S) Zustand haben.

In dem kommenden Posts der Blogserie möchten wir uns tiefergehend mit den verschiedenen Bereichen der IT Security beschäftigen. In insgesamt neun Blogposts möchten wir tiefergehend auf verschiedene Szenarien eingehen, mögliche Schutzmechanismen präsentieren und diese – wenn möglich – durch praktische Beispiele demonstrieren. Nach Blogpost #9 werden wir diese Serie und weitere Tipps und Tricks als PDF zur Verfügung stellen. Sie können sich unter dem nachfolgenden Formular zum Newsletter anmelden, um das PDF zu erhalten:

[newsletters_subscribe form=3]

Das HTTP(S)-Protokoll und der Zustand

HTTP-Architektur
HTTP Architektur. Quelle: Rohit Patil auf Medium

Um es auf den Punkt zu bringen: Das HTTP(S)-Protokoll ist zustandslos. Das bedeutet, dass eine Anfrage eines Clients in dem Moment „vergessen“ wird, in dem der Server mit „Erfolg“ oder „Fehler“ (mehr dazu weiter unten) antwortet. Jeder Request (egal ob subsequente Anfragen des gleichen Clients oder nicht) wird von dem Server also so behandelt, als wäre die Anfrage „neu“.

Durch Cookies kann dem HTTP(S)-Protokoll jedoch ein gewisser Zustand verliehen werden. Cookies sind kleine Textinformationen die mit dem HTTP(S)-Header zwischen Client und Server ausgetauscht werden. Dabei besteht ein Cookie aus den folgenden Informationen:

  • Name: der Name des Cookies, der eine Information trägt
  • Wert: die Information, die zwischen Client und Server ausgetauscht wird
  • Domain: die Domain, auf die das Cookie gesetzt wird
  • Pfad: der Pfad auf einer Domain
  • Expires: die Gültigkeitsdauer des Cookies
  • Max-Age: die Höchstdauer des Cookies
  • HttpOnly: Cookies für JavaScript unerreichbar machen
  • Secure: HTTP oder HTTPS

Set-Cookie und Cookie Header

In dem HTTP(S)-Header können zwei Felder genutzt werden, um Cookies zu setzen: Set-Cookie und Cookie. Dabei wird der Set-Cookie-Header von dem Server genutzt um Cookies dem Client zu übertragen:

$ curl -i https://www.google.com
HTTP/2 200
date: Sun, 17 Jul 2022 10:32:13 GMT
content-type: text/html; charset=ISO-8859-1
set-cookie: theme=light; expires=Fri, 13-Jan-2023 10:32:13 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax

Der Client wiederum nutzt den Cookie-Header, um die gesetzten Cookies an den Server zu übertragen:

$ curl -iv https://www.google.com
GET google.com HTTP/2
Host: https://www.google.com
Cookie: theme=light

Cookies werden genutzt um Sessions zwischen Client und Server zu starten. Sessions sind (meist) kurze Sitzungen zwischen Client und Server, um bspw. einen Warenkorb in einem Online-Shop über Anfragen hinweg erhalten zu haben.

Warum Cookies keine Lösung sind

Dank dem erweiterbaren Design des HTTP(S)-Protokolls könnte man auf die Idee kommen, Cookies maximal auszunutzen um den Zustand einer Anwendung erhalten zu lassen. Zusammen mit HTTP 1.1, welches mehrere Anfragen pro aktiver Verbindung zu einem Server, könnte so – theoretisch – eine performante und zustandsbehaftete Anwendung erstellt werden.

Leider sieht dies in der Praxis nicht ganz so aus. Zunächst einmal gibt es keine Garantie darüber, ob ein Cookie auf Clientseite gespeichert wurde. Des Weiteren ist die Verweildauer eines Cookies im Bestfall wie in dem Set-Cookie-Header spezifiert – eine Anwendung, der Browser oder aber auch der Benutzer kann jederzeit das Cookie löschen, modifizieren oder sonst etwas damit machen. Und letztlich sind Cookies kleine Texte (meist 4KB) und in ihrer Anzahl begrenzt. Sowohl für eine spezifische Domain als auch in der Anzahl aller Cookies, die ein Browser speichert, besteht eine Obergrenze. Wird diese erreicht, wird das älteste Cookie gelöscht und das neue gesetzt.

Cookies bringen eine gewisse „Altlast“ mit sich

Wie bereits erwähnt gibt es keine Möglichkeit zu prüfen ob ein Cookie gesetzt ist oder noch vorhanden ist. Der einzige Weg ist der Reload der Webpage und das Prüfen, ob das Cookie vorhanden bzw. mit dem erwarteten Wert gesetzt ist. Des Weiteren wird manche Logik zwischen Client und Server von dem Client (z.B. Browser) oder Webserver implementiert – sind Voraussetzungen nicht erfüllt, wird das Cookie nicht an den Server übertragen, obwohl es gesetzt ist. Ein Entwickler oder Admin verbringt dann nicht selten viel Zeit mit der Frage nach dem Warum.

Das einfachste Beispiel ist das Secure-Flag des Cookies. Wird die Anfrage über das unsichere HTTP anstelle HTTP(S) übertragen, ist das Cookie nicht einmal Teil des Requests. Ähnliches gilt auch für das Same-Site-Flag: ist dieses Flag so gesetzt, dass Cookies nur von der Erstanbieter-Page lesbar sind, kann u.U. das Cookie nicht an den Server übertragen werden (z.B. über ein iFrame).

Zustandslosigkeit ist der Schlüssel zur Skalierung

Ein weiterer Grund, weswegen die Zustandslosigkeit des HTTP(S)-Protokolls so strikt wie möglich befolgt werden sollte, ist das Skalierungs-Potenzial. Bei zustandsbehafteten Anfragen über mehrere Server hinweg, orchestriert durch einen Load-Balancer, stehen oft vor dem Problem: wie mache ich den Zustand über alle Server hinweg bekannt? Dies gilt ebenso für die Applikationslogik, die die Anfragen entgegennehmen und verarbeiten sollen.

HTTP(S) und Zustand: Best Practices und Lösungsansätze

Das wichtigste zuerst: Es gibt keine Standardlösung! Während in gewissen Szenarien, vor allem kleinen Anwendungen der Session/Cookie-Weg ein durchaus erprobter Weg ist, kann dies für eine Banking-Anwendung schon ganz anders aussehen. Es spielen viele wichtige Faktoren zusammen, so z.B. ob Sie Drittanbieter-Lösungen (OAuth von Drittparteien) vertrauen, wie viel Information Sie preisgeben möchten (z.B. einen Authentifizierungs-Token als Teil des Headers) oder ganz einfache Sachen wie z.B. die Umgebung, in der die Anwendung laufen soll (Intranet, Internet, etc).

Dennoch möchten wir Ihnen einige Best Practices und Lösungsansätze mitgeben, die sich für uns gewährt haben:

  1. Gestalten Sie Ihre Anwendungen und API’s so zustandslos wie möglich: meiden Sie Cookies und Sessions, wo möglich. Setzen Sie auf OAuth, JWT Token oder eigene Authentifizierungstoken. Schicken Sie diese mit jeder Anfrage an den Server und lassen Sie den Server die Berechtigungen zu der gewünschten Aktion prüfen.
  2. Nutzen Sie ausschließlich verschlüsselte Kommunikation: setzen Sie auf jeden Fall auf SSL/TLS. Verweigern Sie unsichere Anfragen und geben Sie niemals Informationen im Klartext heraus. Generieren Sie Token selbst, achten Sie darauf, dass niemand aus Ihnen den Token auf die Identität des Benutzers zurückführen kann. Sie können einzelne Token widerrufen, falls Sie der Meinung sind dass diese entwendet wurden.
  3. Machen Sie sich mit HTTP(S) Verben und REST vertraut: nutzen Sie bspw. GET nicht um sensible Daten zu übertragen – GET-Parameter werden der URL hinzugefügt und landen in diversen Logs oder Anwendungen. Nutzen Sie POST um eine Resource zu erstellen, PUT um diese zu verändern und PATCH um sie teilweise zu updaten. Verweigern Sie alle Anfragen die dieser Logik nicht folgen.
  4. Nutzen Sie HTTP(S) Status Codes: Es ist immer wieder verwunderlich wie viele Anwendungen mit einem HTTP OK (200) antworten und die eigentliche Response über die gewünschte Operation in den Body „verstecken“. Vermeiden Sie diesen Ansatz, da dies auch einen gewissen (und schlechten) „Zustand“ simuliert und gewisse Kenntnisse Ihrer API’s voraussetzt. Viele Anwendungen und Browser haben Bordmittel zur Fehlerbehandlung (z.B. für 404 oder 500). Wenn diese jedoch nie auftreten, müssen Sie sich selbst darum kümmern.
  5. Haben Sie ein Auge auf verdächtigen Anfragen: Sie sollten sehr früh in der Validierung der Anfrage darauf achten, dass Sie verdächtige Anfragen ausfiltern. Dies kann neben Anfragen ohne Header oder Body, sicherheitskritischen Parametern (z.B. XSS) auch einfach eine erhäufte Anzahl von Anfragen pro Sekunde sein. Je nach Art und Qualität der Anfrage können diese Art von Attacken (DDoS) Ihre Server schnell in die Knie zwingen. Führen Sie RateLimiter ein und sperren Sie verdächtige IP-Adressen (für eine gewisse Zeit) aus. Haben Sie eine zustandslose Architektur, müssen Sie nicht den Inhalt der Anfrage auswerten.

Fazit

Wie aus dem Blogpost heraus geht, haben wir sehr gute Erfahrung mit zustandslos API’s gemacht und sehen die Vorteile gegenüber zustandsbehafteten Anwendungen überwiegen. Mit unserer Software-as-a-Service (kurz: SaaS) Lösung Keestash bspw. haben wir nicht nur für sichere API’s gesorgt, sondern konnten durch mit nahezu keiner Anpassung auf API-Seite mobile Clients (für iOS und Android) erstellen. Dank der zustandslosen Natur der API sind die Clients (Web-Frontend, mobile Anwendungen) nahezu komplett unabhängig von einander bzw. der API.

Keestash API Integration
Keestash API Integration

Benötigen Sie Beratung, Umsetzung oder Audits in dem Bereich IT-Security? Sprechen Sie uns noch heute an, wir freuen uns auf Sie!