Sessions mit Rails, aber ohne Cookies

In einem unserer Projekte liefern wir Seiten aus, die als IFrame auf vielen anderen Seiten eingebunden werden. Diese einbindenden Seiten liegen auf verschiedenen Domains, die sich in der Regel von der IFrame-Domain unterscheiden. Dadurch wird der IFrame-Inhalt zum sogenannten Drittanbieter.

Browser wie der aktuelle Safari (v7.0, iOS und OSX) verbieten standardmäßig das setzen von Cookies von einem Drittanbieter. Zudem schalten viele Benutzer anderer Browser diese "Drittanbietercookies" ebenfalls aus - und das nicht ohne Grund: Sie werden nämlich auch gerne von Werbetreibenden genutzt, um Benutzer zu tracken.

Da wir aber mehrseitige Formulare und andere Inhalte in diesen IFrames ausliefern, die den Benutzungszustand halten müssen, ist es notwendig, dass die Benutzer eine Session bekommen. Ansonsten funktioniert unsere Anwendung nicht ordnungsgemäß.

Im Rails-Framework ist es normalerweise nicht möglich Sessions ohne Cookies zu verwenden. Es gibt verschiedene Möglichkeiten, mit dem Problem umzugehen. Die Lösung die wir für unsere Anwendung gewählt haben, möchten wir im Folgenden vorstellen.

Lösungsansatz

Wir greifen auf eine Technik zurück, die vielen vielleicht noch aus Urzeiten der PHP-Entwicklung bekannt sein dürfte: Die Session-ID als URL-Parameter in der Adresse anzuhängen.

Diese Möglichkeit wird von Rack-Sessionverwaltung unterstützt, ist jedoch bei den bei Rails mitgelieferten Session Stores wie CookieStore, ActiveRecordStore und MemCacheStore aus Sicherheitsgründen abgeschaltet. Wir weichen deswegen auf das performante RedisSessionStore aus, das die Session-Daten in einer Redis-Datenbank verwaltet. Dadurch können wir die in Rack eingebaute Funktionalität, die Session-ID aus dem URL-Parameter auszulesen, verwenden. Rack geht dabei wie folgt vor:

  • Sendet der Client ein Cookie, das wie der Session-ID-Schlüssel heißt (etwa "_myapp_session"), wird die ID aus diesem Cookie verwendet.
  • Gibt es kein derartiges Cookie, werden die GET-Parameter untersucht. Befindet sich hier der Schlüssel, wird die ID aus dem Parameter verwendet (z.B. …/?_myapp_session=46e84a30f8dbda19ab0bd3c379035c51).
  • Liegt keine Session-ID vor, wird eine neue Session initialisiert.

Sicherheit

Wer Kenntnis über die Session-ID eines Nutzers einer Webanwendung hat, kann – ohne dass Sicherheitsmaßnahmen implementiert sind – die Identität des Nutzers übernehmen. So lassen sich sensible Nutzerdaten einsehen oder es erlaubt gar einem Dritten, privilegierte Aufgaben durchzuführen, zu denen nur der ursprüngliche Inhaber der Session-ID berechtigt war.

Die Session-ID in einem GET-Parameter kann durch unterschiedliche Ursachen unabsichtlich preisgegeben werden:

  • Wird ein externer Link aufgerufen, steht die ID im Referrer und kann vom externen Server genutzt werden. In den meisten Fällen wird der Referrer auch in den Webserverlogs lange gespeichert.
  • Kopiert man eine URL, die eine Session-ID beinhaltet und gibt sie weiter, wird jeder, der diese URL verwendet, ebenfalls die selbe Session-ID nutzen können. Viele Nutzer kennen dieses Risiko beim Kopieren der URL nicht.

Um dieses sogenannte Session Hijacking zu erschweren, verwenden wir das Gem Frikandel. Mit ihm lässt sich die Lebenszeit einer Session einschränken und jede Session an eine IP-Adresse binden.

  • Beschränkt man die Lebenszeit, ist die Session bestenfalls bereits abgelaufen, bevor ein Dritter diese verwenden kann.
  • Wird versucht, die Session-ID von einer anderen IP-Adresse zu verwenden, wird der Zugriff auf diese Session verweigert.

Da wir hiermit aber keinen 100%-igen Schutz vor Session Hijacking bieten können, haben wir Session-ID per GET-Parameter nur dort aktiviert, wo keine sensiblen Daten verarbeitet werden und es für Bereiche deaktiviert, in denen administrative Aufgaben durchgeführt werden können.

Rails 3.2.x Bug

Bei Tests in Verbindung mit Frikandel bzw. reset_session haben wir einen Bug in Rails 3.2.x festgestellt. Dieser ließ sich glücklicherweise recht einfach testen und beheben. Es war jedoch leider sehr aufwändig, den Fehler zu identifizieren:

Bei einem Zurücksetzen der Session in Rails 3.2.x mit reset_session, wird die aktuelle Session gelöscht und eine neue, leere mit einer neuen ID erzeugt. Der Fehler bestand nun darin, dass die ID der neu erzeugten Session nicht zurückgegeben bzw. gesetzt wurde. Dies führte dazu, dass keine Session-ID als URL-Parameter mitgegeben werden konnte und damit die Session-Daten (z.B. flash-Meldungen) der letzten Aktion verloren gingen.

Gem

Unsere Lösung für Rails haben wir als wiederverwendbares Gem veröffentlicht: cookieless_sessions. Beim Einsatz sollten die oben erwähnten Sicherheitsbedenken beachtet werden, wir empfehlen auf jeden Fall den zusätzlichen Einsatz weiterer Sicherheitsmaßnahmen, wie z.B. Frikandel.

Weitere Informationen hierzu und wie das Gem in eine Rails-Anwendung einbindet, sind auf der Projektseite auf GitHub zu finden.



Zur Blog-Übersicht