LOVOO: Sicherheitslücken erlaubten Zugriff auf fremde Accounts

Die Plattform Lovoo war bis Ende Juni 2020 von einigen teils gravierenden Sicherheitslücken betroffen. Die Schwachstellen betrafen die Webanwendung lovoo.com und erlaubten einen uneingeschränkten Zugriff auf fremde Accounts. In diesem Beitrag werde ich von meinen Funden nach dem Responsible-Disclosure-Prinzip berichten. Alle hier beschriebenen Lücken wurden bereits behoben.

Was ist Lovoo? – Ein kurzes Briefing

Bei Lovoo handelt es sich um ein soziales Netzwerk mit dem Fokus auf Online Dating. Hinter Lovoo steckt die Lovoo GmbH, eine deutsche Firma mit Sitz in Dresden. Laut eigenen Angaben wurde die Plattform im Jahr 2014 von rund 10 Millionen Nutzern verwendet. 2015 waren es bereits 30 Millionen Nutzer. Aktuellere Angaben zu den Nutzerzahlen waren mir nicht auffindbar. Die Plattform ist als App für Android und iOS sowie als Webseite nutzbar. Gewinn macht die Plattform unter anderem durch den Verkauf von Premium-Mitgliedschaften und Credits für die Livestreaming-Funktion Lovoo-Live.

Sehr wichtig ist allerdings, dass man Lovoo richtig ausspricht. Das erste O einfach als A aussprechen und die restlichen O’s als U. Dann passt das 🙂

Lücke #1 – Subdomain Takeover

WordPress ist schon ein tolles Content Management System. Dieser Blog hier verwendet auch WordPress – bin recht zufrieden damit. Es gibt zwei Möglichkeiten eine WordPress-Seite zu betreiben. Self-hosted auf einem eigenen Server bzw. Webspace oder “online” über wordpress.com. Bei der letzteren Option benötigt man keine technischen Ressourcen oder Kenntnisse. Lovoo hat sich für mehrere Seiten bei wordpress.com entschieden. Man bekommt dann automatisch eine beliebige Subdomain bei wordpress.com zugewiesen. Zum Beispiel wäre dieser Blog unter buglog.wordpress.com auffindbar, sofern ich mich für diese Variante entschieden hätte. Das ist natürlich für Firmen eher unschön, da diese in der Regel über eine eigene Domain verfügen. Daher bietet WordPress das “aufschalten” von Domains an. Dabei ist es möglich, per CNAME-Eintrag von der eigenen Domain auf die WordPress-Seite zu verweisen. Dazu muss in den DNS-Einstellungen der jeweiligen Domain ein CNAME-Eintrag auf wordpress.com angelegt werden. In den Einstellungen der WordPress-Seite muss wiederum die (Sub-)Domain angegeben werden. Damit ist ein Domain-Mapping hergestellt.

Folgende WordPress-Seiten waren im Besitz von Lovoo:

  • stories2.lovoo.com
  • tech.lovoo.com

Anstelle einer wunderbaren Webseite erhielt man allerdings einen 404-Fehler bei Aufruf der beiden Subdomains.

Daraus ging hervor, dass die beiden WordPress-Seiten zwar nicht mehr existierten, es allerdings noch einen CNAME-Eintrag zu wordpress.com gab.

Um zu testen, ob WordPress anfällig für Subdomain-Übernahmen ist, registrierte ich kurzerhand meine eigene WordPress-Seite. Und tatsächlich konnte ich durch das Eintragen der Subdomain stories2.lovoo.com ein vollständiges Domain-Mapping herstellen und meine Webseite darunter veröffentlichen. Der vorhandene CNAME-Eintrag wurde ohne weiteres akzeptiert.

Proof of Concept

Die Gefahr bei solchen Subdomain-Takeover-Angriffen liegt in erster Linie im Bereich Phishing. Aber auch das Entwenden von Cookies kann in bestimmten Fällen hiermit erzielt werden. Dies war auch bei Lovoo der Fall. Das Session-Cookie mit der Bezeichnung lovoo dient zur maßgeblichen Authentifizierung des Benutzers und wird beim Login als “Subdomain-Wildcard” gesetzt, was dazu führt, dass dies auch bei Anfragen an sämtliche Subdomains übermittelt wird.

Session-Cookies lovoo wird auch für externe Subdomains gesetzt.

Nun ist natürlich die Frage, ob man hier kein Risiko sieht oder ob das schlichtweg nicht bedacht wurde. Auf Nachfrage teillte Lovoo mit, dass diese Cookie-Konfiguration aufgrund der lokalisierten Subdomains gewählt wurde. Bspw. gibt es die Subdomains it.lovoo.com für Italien und ru.lovoo.com für Russland. Es ist jedenfalls mutig, die Benutzer-Session an WordPress oder Shopify zu senden, wenn der Nutzer bspw. ein paar “Credits” einkaufen möchte.

Unter https://premium.lovoo.com/ befindet sich ein Shopify-Shop

Aber darum soll es nun auch nicht weiter gehen, da das Session-Cookie zumindest als “http-only” gesetzt wird und ich es somit eh nicht von meiner aufgeschalteten WordPress-Seite per JavaScript auslesen konnte. Stattdessen setzte ich auf Phishing und baute mir einen täuschend echt aussehenden Login-Screen, welchen ich auf meiner WordPress-Seite platzierte. Da eine Subdomain nicht mehrmals “übernommen” werden kann, stand meiner Ansicht nach einer öffentlichen Herangehensweise nichts im Wege. Somit beschloss ich, Lovoo direkt per Twitter zu kontaktieren.

Tatsächlich wurde die Lücke noch am gleichen Abend behoben. Zu meiner Verwunderung bedankte sich Lovoo mit einem Bug Bounty bei mir. Das gab mir die Motivation, nach weiteren Lücken zu suchen.

Lücke #2: Meta-Tag Manipulation via Benutzername

Meta-Tags geben Informationen über die Webseite. Diese bewirken z.B. eine bessere Auffindbarkeit über Suchmaschinen (Stichwort SEO). Auch Lovoo verwendet solche Meta-Tags. Bei Aufruf eines Benutzerprofils über lovoo.com/profile/{userId} werden unter anderem folgende Meta-Tags gesetzt:

Wie man sehen kann, wird unter anderem der Benutzername im HTML-Quelltext reflektiert. Mein erster Test bestand daraus, das Content-Attribut mit einem Anführungszeichen im Benutzername zu schließen.

Anführungszeichen konnten ohne Probleme gesetzt werden. Neue HTML-Tags konnten jedoch nicht gespeichert werden, da < scheinbar geblacklistet ist.

Wie man sehen kann, wurde das Anführungszeichen nicht korrekt kodiert, was zum Ausbruch aus dem Stringliteral geführt hat. Die Herausforderung bestand nun darin, eine funktionierende Payload (Schadcode) zu erstellen, welche ohne die Eröffnung neuer HTML-Tags funktioniert. Spontan fiel mir die Redirect-Funktion in HTML ein. Auch bekannt als Meta-Refresh. Hierbei kann man mithilfe eines Meta-Tags den Benutzer auf eine andere Webseite leiten. Die Weiterleitung erfolgt ohne Bestätigung.

<meta http-equiv="refresh" content="0;url=https://zerody.one/">

Das Ganze übersetzt in eine funktionierende Payload:

0;https://google.com" HTTP-EQUIV="refresh" x="

Ergab nach dem Setzen des Benutzernamens:

Und tatsächlich erfolgte beim Aufruf des Profils eine Weiterleitung auf Google! Ein Angreifer würde hier natürlich nicht auf Google, sondern auf bspw. eine Phishing-Seite leiten.

Lücke #3: XSS via Wohnort

Damit man andere User in der Region findet, bietet Lovoo eine Wohnort-Einstellung. Es lassen sich über das Interface jedoch nur valide Städtenamen setzen, welche man aus den Vorschlägen anwählen kann. Leider gibt es offiziell noch keine Stadt mit einer XSS-Payload im Namen. 🙁

Die Validierung des Stadtnamen erfolgt jedoch nur Client-Side über das Abrufen der Vorschläge. Gibt es keine Vorschläge, lässt sich der Wohnort nicht speichern.

Aber der Wohnort wird dann auch beim Speichern Server-Side validiert? Nein, wird er nicht. Diese Information wird ebenfalls in den Meta-Tags reflektiert und im Gegensatz zum Benutzername gab es auch keine Zeichenbeschränkung. Es konnte somit problemlos < und > verwendet werden. Damit war der Grundstein für eine kritische Cross-Site-Scripting-Lücke gelegt. Beim Setzen der XSS-Payload passierte dann aber etwas unerwartetes.

Anstelle eines 200 OK-Response erhielt ich einen 403 Forbidden-Response. Scheinbar blockierte die Web-Application-Firewall (WAF) von Cloudflare den Request mit der XSS-Payload. Eine WAF kann potentiell gefährliche HTTP-Anfragen blockieren. Klassische Angriffsvektoren wie Cross-Site-Scripting und SQL-Injections können hiermit mehr oder weniger effektiv verhindert werden.

Um dennoch einen Angriff durchführen zu können, wäre entweder ein WAF-Bypass oder schlichtweg die Umgehung von Cloudflare nötig. Ich entschied mich für letzteres. Eine Suche auf Censys ergab recht schnell einige Ergebnisse.

Bei der Suchanfrage wurde schlichtweg nach IPv4 Hosts mit einem SSL-Zertifikat für lovoo.com gesucht. Viele Webseitenbetreiber vergessen die Tatsache, dass die meisten Webserver ohne eingerichteten vHost auch das Zertifikat bei direktem IP-Zugriff exposen. Das wurde Lovoo hier zum Verhängnis. Dennoch konnte man Lovoo nun nicht einfach mit der IP hinter Cloudflare aufrufen. Scheinbar hatte man vor knapp einem Jahr auf meinen Tweet vom 14.08.2019 reagiert und nur noch Zugriffe seitens der bekannten Cloudflare-IP-Ranges zugelassen.

Ich hatte mir nun also selbst Steine in den Weg gelegt. Wenn man Lovoo jedoch früher ohne gültigen Host-Header aufrufen konnte, dann scheint es auch keine vHosts zu geben? 🤔 Und wenn sie nur Zugriff seitens Cloudflare wollen, dann sollen sie diesen auch bekommen. Aber dann halt über meinen privaten Cloudflare-Account, welcher den Free-Plan nutzt und somit auch keine WAF besitzt. Kurzerhand richtete ich mir eine Subdomain ein, welche ich auf die bei Censys gefundene IP zeigen ließ.

Und siehe da, bei Aufruf der Subdomain erhielt ich die Startseite von Lovoo. Nun kam der große Moment und ich versuchte die XSS-Payload erneut über meine Subdomain zu setzen.

YES, der Request ging durch und die Payload im Feld “Wohnort” wurde gespeichert. Ein Blick in den Seitenquelltext bestätigte dies.

Was würde nun passieren, wenn man das Profil aufruft?

Bingo!

Nach dem Melden der beiden Lücken, wurden diese bereits am nächsten Tag geschlossen sowie ein weiteres Bug Bounty ausgezahlt. Ich persönlich stufte die XSS-Lücke als sehr gefährlich ein, da hierüber Accounts gekapert werden konnten. Lediglich musste das Opfer ein manipuliertes Profil besuchen. Dass es auch XSS-Lücken gibt, welche keinerlei Nutzerinteraktion benötigen, ahnte ich zu dem Zeitpunkt noch nicht.

Lücke #4: XSS via AngularJS-Expressions

Lovoo verwendet im Frontend das Framework AngularJS und im Backend das PHP-Framwork Symfony. Das AngularJS-Frontend bedient sich größtenteils an einer JSON-API. Das Sanitizing (unschädlich machen) der Informationen sollte somit bestenfalls im Client erfolgen. Ich selbst bin kein Angular-Entwickler und kann daher nicht wirklich beurteilen, was im folgenden konkret falsch gelaufen ist.

Zuerst fiel mir auf, dass man in der Profilbeschreibung (freetext), welche ebenfalls öffentlich ist, HTML-Tags verwenden konnte, welche unter anderem im Messenger des Chatpartners interpretiert wurden.

Die Payload <img src=x onerror="alert(0)" /> funktionierte hier aber nicht – Das onerror-Attribut wurde schlichtweg beim Laden des Inhalts Client-side entfernt. Auch andere Payloads wurden unschädlich gemacht. Nach kurzer Recherche ist dies scheinbar ein Security-Feature von AngularJS.

Dennoch ist allein das Einbinden von externen Grafiken sehr problematisch, da der Browser immer einen Request an die bei src angegebene URL sendet und somit das ermitteln von IP-Adressen möglich ist.

Als nächstes versuchte ich Angular-spezifische Payloads. Hier gibt es sogenannte AngularJS-Expressions, welche die Ausführung von einfachen Anweisungen ermöglichen. Zum Beispiel würde bei {{1+1}} eine Interpretation von AngularJS erfolgen, so dass der Text automatisch durch 2 ersetzt wird. Aber klappt das auch auf Lovoo?

{{constructor.constructor('alert(1)')()}}

…führte zunächst nicht zum Erfolg. Bei dieser Payload würde zudem ein Ausbruch aus der AngularJS-Sandbox erfolgen, so dass die ungehinderte Ausführung von JavaScript möglich wäre. Stattdessen wurde die Payload erneut beim Laden des Inhalts unschädlich gemacht, indem die ersten beiden geschweiften Klammern nur client-side entfernt wurden. Mich machte dieses Verhalten stutzig, da die restliche Payload weiterhin ins DOM eingesetzt wurde. Ich entschied mich nach der Codestelle zu suchen, welche diese Anpassung durchführt.

Auszug aus der JavaScript-Datei lovoo.min.js

Was man hier im Scrennshot sieht, faszinierte mich sehr. Aus den Profil-Attributen name, freetext und email wurde schlichtweg die Zeichenkette {{ durch eine Leerstring-Ersetzung entfernt. Um zu verstehen, wo hier das Problem liegt, muss man einmal einen Blick auf die Replace-Funktion in JavaScript werfen.

Note: If you are replacing a value (and not a regular expression), only the first instance of the value will be replaced. To replace all occurrences of a specified value, use the global (g) modifier.

Quelle: https://www.w3schools.com/jsref/jsref_replace.asp

Heißt konkret: Hier wird nur das erste Vorkommen von {{ entfernt.

Die nicht funktionierende Payload konnte ich somit um zwei geschweifte Klammern ergänzen:

{{{{constructor.constructor('alert(1)')()}}

Baaam! 🙂

Die XSS-Payload wurde beim Gegenüber allein durch das Aufrufen der Chat-Konversation ausgeführt. Es ist dennoch eine Benutzerinteraktion erforderlich. Geht das noch besser? 😉

Beim Besuch von lovoo.com landet man zuerst immer auf einer “Entdecken”-Seite, welche “Leute” aus der Region anzeigt, die zu den Sucheinstellungen passen. Eine Stored-XSS-Lücke auf der “Startseite” wäre natürlich der Knaller. Die Vorschläge auf der Startseite enthalten den Benutzernamen, das Alter und den Wohnort. Das Alter kann für XSS ausgeschlossen werden, da es sich um einen Integer handelt. Somit kommt nur der Benutzername und der Wohnort in Frage.

Ich entschied mich die zuletzt verwendete Payload als Benutzername zu setzen. Mit einem anderen Account begab ich mich auf die Startseite, um nach meinem präparierten Profil zu suchen.

Es funktionierte, die Payload wurde beim Scrollen durch die Vorschläge ausgeführt.

Damit war für mich das XSS-Game “Lovoo” durchgespielt.

Was hätte passieren können?

Cross-Site-Scripting-Lücken gelten als recht gefährlich, da diese die Ausführung von JavaScript im Kontext der Webseite ermöglichen. Dadurch können bspw. Benutzerdaten abgegriffen und verändert werden, da eine ungehinderte Kommunikation mit der API im Kontext des Benutzers möglich ist. Durch die Präparierung eines einzigen Benutzerprofils, wäre eine Ausführung von JavaScript bei allen Profilbesuchen über die Webseite möglich gewesen. Bei der zuletzt genannten Lücke, wäre es zur Ausführung gekommen, sobald das Benutzerprofil jemanden auf der Webseite vorgeschlagen wird.

Bei einer wurmartigen Verbreitung hätte die XSS-Payload bei der Ausführung wiederum andere Benutzerprofile durch eine automatisierte Änderung des Benutzernamens “infizieren” können, so dass diese ebenfalls die Payload beinhalten. Dadurch wäre es möglicherweise zu einer exponentiellen Verbreitung des Schadcodes gekommen, was das massenhafte Abgreifen von Daten prinzipiell ermöglicht. Eine Übernahme des Accounts wäre durch die Änderung der hinterlegten E-Mail-Adresse möglich gewesen.

Inzwischen hat Lovoo weitere Schutzmaßnahmen implementiert. Eine Änderung der hinterlegten E-Mail-Adresse ist bspw. nur noch durch vorheriger Bestätigung der aktuellen E-Mail-Adresse möglich.

Sehr gute und schnelle Reaktion

Neben den genannten Lücken, meldete ich auch noch einige andere, weniger kritische Lücken. Alle Lücken wurden innerhalb eines Tages behoben – und das selbst am Wochenende. Das LOVOO Entwickler-Team, welches unter dem Twitter-Profil @LOVOOEng erreichbar ist, konnte die Schwachstellen korrekt einschätzen und führte eine größtenteils offene Kommunikation mit mir.

Zudem hat sich LOVOO nun ganz offiziell dazu entschlossen, ein Bug-Bounty-Programm zu betreiben, wodurch sicherheitsrelevante Meldungen durch die Auszahlung von Bug-Bounties honoriert werden.

Regeln und weitere Infos: https://www.lovoo.com/security

Fazit

Es war schon ein wenig überraschend, dass man auf so einer großen Plattform auf so gravierende Sicherheitsmängel stößt. Da sieht man, dass es sich auch mal lohnt, die etwas größeren Webseiten unter die Lupe zu nehmen. Alles in allem fand ich das Suchen der Bugs sowie die Kommunikation mit LOVOO sehr spannend. Daher die Motivation ein halbes Buch zu schreiben 🙂

Bounty: 1500€

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.