Die potenzielle Cross-Site-Request-Forgery-Sicherheitslücke bei Custom Liferay Portlets

Eine Default-Sicherheitslücke für Cross-Site-Request-Forgery-Angriffe in Liferay kann zu einem Problem werden, je nachdem, mit welchem Framework man sein Portlet realisiert und welche Anforderungen umgesetzt werden müssen. Ermöglicht wird das durch die Details der Implementierung, die eigentlich dafür vorgesehen sind, diese Angriffe abzuwehren. Ob man von diesem Problem betroffen ist und was man dagegen unternehmen kann, soll dieser Artikel zeigen.

Cross-Site-Request-Forgery (CSRF)-Angriffe sind Aktionen, die einem authentifizierten User ungewollt untergeschoben werden.

Cross-Site Forgery

Dabei ist es nicht zwingend notwendig, dass zuvor z. B. die Web-Applikation mittels XSS kompromittiert wurde. Es reicht manchmal schon aus, auf einen präparierten Link in einer scheinbar vertrauenswürdigen E-Mail zu klicken. Die Auswirkungen dieser Angriffe können sehr weitreichend sein. Das Risikopotenzial sollte jeder Entwickler abschätzen, indem er davon ausgeht, dass jede implementierte Aktion ein potenzielles Angriffsziel darstellt.

Was bietet Liferay gegen CSRF-Angriffe?

Ganz wehrlos ist man als Liferay/Portlet-Entwickler nicht. Aktuelle Versionen von Liferay implementieren das Synchronizer Token Pattern, um CSRF-Angriffe zu verhindern. Standardmäßig wird bei allen ActionRequests geprüft, ob das in der URL enthaltene Token dem Gegenstück in der Session entspricht. Die Generierung und Prüfung dieses Tokens übernimmt eine entsprechende Implementierung des Interfaces com.liferay.portal.security.auth.AuthToken., die unter der Portal Property auth.token.impl konfiguriert werden kann. Das Open Web Application Security Project (OWASP) empfiehlt diese Art der Absicherung. Das lesenswerte Prevention Cheat Sheet bietet einen Überblick der existierenden Abwehrstrategien und beschreibt auch, aus welchen Gründen einige Methoden gänzlich ungeeignet sind.

Gefahr erkannt Gefahr, Gefahr gebannt?

Auf den ersten Blick sieht es so aus, dass damit das Problem mit CSRF-Angriffen in Liferay bereits gelöst ist und man sich als Entwickler keine Sorgen mehr machen muss. Leider stimmt das nicht immer.
Manche Situationen erfordern, dass bei der Prüfung des Tokens Ausnahmen gemacht werden. Hierfür gibt es in der portal.properties-Datei von Liferay zwei Konfigurationsmöglichkeiten. Mit dem Eintrag der Portlet ID als Wert für auth.token.ignore.portlets kann die Prüfung für einzelne Portlets deaktiviert werden. Ausnahmen können aber auch für einzelne Actions definiert werden. Die Liferay-Standardkonfiguration beinhaltet bereits eine ganze Liste mit mehr als 10 Ausnahmen für Portlets, die mit Liferay ausgeliefert werden. Hier ein kleiner Auszug:

Die Action /login/login z. B. ist aus folgendem Grund hier aufgeführt: Während der Anmeldung wird die bestehende Session geschlossen und eine neue Session wird angelegt. Dieses Vorgehen ist nötig, um Session Fixation Angriffe zu verhindern. Weil die Session quasi zerstört wird, kann aber kein Vergleich mit dem Token stattfinden. Sicher gibt es bei den anderen aufgeführten Aktionen triftige Gründe für, dass hier ebenfalls eine Ausnahme nötig ist.

Besonders wenn man ein eigenes Login-Portlet umsetzen möchte oder bestimmte Actions auch dann aufgerufen werden können müssen, wenn z. B. die Session abgelaufen ist, müssen entsprechende Ausnahmen konfiguriert werden. Hierbei muss bewertet werden, ob das ganze Portlet auf die Ignore-Liste gesetzt werden kann oder nur einzelne Actions von der Prüfung ausgenommen werden.

Liferay 6.2.0 GA1 Implementierungsdetails

Ein Blick in den Quellcode, genauer: in die relevante Methode AuthTokenWhitelistImpl.isPortletCSRFWhitelisted(long, String, String) , zeigt wie diese Prüfung umgesetzt wurde:

Neben der Prüfung, ob die whitelistActions-Liste die strutsAction enthält, wird auch geprüft, ob es eine gültige strutsAction ist. Diese zusätzliche Prüfung verhindert, dass der Parameter bei Nicht-Struts-Portlets für die Umgehung des Cross-Site-Request-Forgery-Schutzes zweckentfremdet wird.

Hinweis für Liferay 6.0.X

Deutlich schlechter sieht die Situation bei einer eventuell noch vereinzelt eingesetzten älteren Liferay Version wie 6.0.12 EE aus. Hier wird letztendlich nur nach einem Request Parameter struts_action mit dem PortletNamespace als Prefix gesucht. Dieser wird dann mit der Liste der ignorierten Actions verglichen. Die Prüfung des Tokens findet dann nicht statt, wenn ein passender Eintrag vorhanden ist.
Für Struts-Portlets ist diese Art der Prüfung hinreichend sicher, weil mit diesem Parameter direkt die auszuführende PortletAction bestimmt wird. Bei den meisten anderen Framewors hingegen wird ein Parameter, der nicht verwendet wird, einfach ignoriert. Somit ist es bei dieser Liferay-Version möglich, die Prüfung für alle Nicht-Struts-Portlets gänzlich auszuhebeln.

Wie können Nicht-Struts-Portlets geschützt werden, wenn Ausnahmen für einzelne Actions nötig sind?
Von den zwei Konfigurationsmöglichkeiten bleibt somit für Nicht-Struts-Portlets nur noch die Abschaltung der Prüfung für das ganze Portlet per Eintrag der PortletId in die Liste auth.token.ignore.portlets. Leider wird dann nicht nur die Prüfung des p_auth Parameters nicht mehr durchgeführt, der Parameter wird gar nicht mehr automatisch generiert. Damit man diesen Parameter in einem ActionFilter für die Prüfung verwenden kann, muss dieser bei sicherheitsrelevanten Actions dieser wieder hinzugefügt werden. Etwa so:

Die Prüfung, ob das Token gültig ist oder die Action ignoriert werden soll, kann dann in einem javax.portlet.filter.ActionFilter implementiert werden.

Insgesamt ist es aktuell etwas umständlich, die skizzierte Anforderung umzusetzen. Sinnvoller wäre es aus meiner Sicht, wenn man die Kontrolle über die Ausnahmen direkt auf Portlet-Ebene hätte. Ich habe einen entsprechenden Feature Request LPS-48358 erstellt und freue mich über jede Unterstützung in Form eines Votes oder ergänzenden Kommentaren. Bis dahin hat man nur die Möglichkeit, die Prüfung im ersten Schritt mit einem entsprechenden Eintrag der PortletId in der Portal Property auth.token.ignore.portlets auszuschalten und selbst zu prüfen, ob es sich um eine Action handelt, die ignoriert werden kann oder nicht.

GitHub Repository mit Code-Beispiel: https://github.com/AndreasFriedel/action-test-portlet

comments powered by Disqus