Zum hauptinhalt springen

Learnu oder: Meine App, die hat drei Lücken 🎶

Dieser Artikel ist der zweite Teil der “Back-To-School-Serie”.

Hausaufgaben – ein leidiges Thema, das so ziemlich alle in sehr schlechter Erinnerung haben oder noch alltäglich ertragen müssen. Viele Schüler*innen versuchen deshalb, möglichst wenige davon zu machen und die Hausaufgaben solidarisch untereinander zu teilen. Doch was alles schief gehen kann, wenn man versucht, daraus ein Geschäftsmodell zu machen, das zeigen wir euch jetzt.

Die Gründer*innen unserer heutigen App wollten aus dem Teilen von Hausaufgaben ein Startup machen und haben sich eine Marktplatz-App für Hausaufgaben ausgedacht: learnu1.

Learnu will alle Schüler*innen glücklich machen: Die einen müssen keine Hausaufgaben mehr machen, sondern können sie in der App einkaufen. Bezahlen können sie entweder mit echtem Geld (5€ im Monat für Zugang zu allen Aufgaben) – oder mit ihrer Zeit und Werbung gucken.

Und die anderen können, wenn sie sich sowieso schon an den Schreibtisch setzten, Geld damit verdienen. Für hochgeladene Hausaufgaben gibt es In-App-Credits (“Learnus”), die gegen Gutscheine eingetauscht werden können.

Von kein Plan zu kaputter App

Kurz nach dem eigenen Abi, zum Start des Studiums 2020, haben die Gründer*innen die Idee dann umgesetzt. Und das kam gut an – die App wurde von mehr als 500.000 Schüler*innen genutzt.

Doch wie sie selbst zugeben, kannten sich die Gründer*innen nicht gut genug mit App-Entwicklung aus und haben die App deshalb entwickeln lassen. Sie selbst haben also die Sicherheitslücken, die wir hier beschreiben, gar nicht fabriziert – aber sie haben andere mit der Entwicklung beauftragt, ohne die Code-Qualität selbst prüfen zu können. Und das kaputte Ergebnis dann an Schüler*innen verkauft.

Denn leider wurde die App technisch so schlecht umgesetzt, dass am Ende die Daten von rund 500.000 Accounts öffentlich zugänglich waren. Dazu gehören:

  • Vor- und Nachname, die bei der Registrierung als Klarname abgefragt werden
  • Username innerhalb der App
  • E-Mail-Adresse
  • Stadt und Schule
  • alle gestellten Fragen
  • alle abgerufenen Antworten

Doch bevor wir das entdeckten, mussten wir uns die App erstmal genau anschauen.

🕵

Dazu haben wir uns, wie immer, erstmal die App installiert und einen Machine-in-the-Middle-Proxy eingerichtet. Mit diesem können wir den Datenverkehr zwischen App und Server mitlesen. Dazu schaltet sich der Proxy dazwischen und behauptet jeweils, die entsprechende Gegenstelle zu sein.

Schaubild wie eine App auf unserem Smartphone mit einem Proxy auf unserem Computer und dieser mit dem Server kommuniziert

Um sicherzustellen, dass ein Angreifer so etwas nicht machen kann, überprüft eine App, dass der Server ein gültiges Zertifikat hat, sich also ausweisen kann.

Normalerweise fragt eine App dazu das Betriebssystem, ob dieses Zertifikat gültig ist. Das Betriebssystem hat eine lange Liste von Stellen, die gültige Zertifikate ausstellen dürfen2 und schaut, ob das Zertifikat von einer Stelle auf dieser Liste ausgestellt wurde.

Das ist für uns super, denn dann können wir uns selbst auf diese Liste schreiben. Damit vertraut unser Handy dann auch allen Zertifikaten, die wir selbst ausgestellt haben.

Die Learnu-App ist jedoch mit dem Framework Flutter entwickelt worden. Flutter greift nicht auf die Liste des Betriebssystems zurück, sondern bringt eine eigene mit. Das liegt vor allem daran, dass Flutter-Apps auf verschiedenen Betriebssystemen funktionieren sollen, aber jedes Betriebssystem einen anderen Weg hat, die Liste mit den Zertifikaten abzufragen. Also ist es einfacher, eine eigene Liste mitzubringen, als für jedes Betriebssystem eine andere Abfrage zu implementieren.

Das klingt erstmal gut, stellt uns jedoch vor ein Problem: Wir können nicht unser Test-Handy nutzen, wo wir uns bereits auf die Liste gesetzt haben, sondern müssen die Liste in der Learnu-App selbst anpassen. Das ist nichts, was uns dauerhaft abhalten kann, aber erschwert unsere Arbeit kurzfristig.

Let’s get binary

Daher schauen wir uns erstmal den Code der App genauer an. Der ist natürlich nicht öffentlich einsehbar. Deshalb müssen wir die App auf den Computer herunterladen, sie dekompilieren und können dann den Binärcode anschauen. Dort haben wir erstmal wild rumgestochert und schnell diese Stelle gefunden:

Screenshot des Binärcode, mit einem Texteditor geöffnet – markiert ist der String “admin.learnuapp.net/api”

Wenn irgendwo “Admin” steht, klingt das für uns natürlich direkt spannend – und wir öffnen die Webseite im Browser, wo wir direkt auf https://admin.learnuapp.net/login weitergeleitet werden. Ein Hoffnungsschimmer, dass wir hier nicht weiterkommen: Die Zugangsdaten unseres Accounts aus der App funktionieren schonmal nicht.

Learnu, öffne dich

Aber aus Erfahrung wissen wir auch: Wenn es die Unterseite /login gibt, dann gibt es sehr wahrscheinlich auch die Unterseite /register. Und zack – wir sind auf dem Registrierungsformular für neue Accounts. Hmmm, vielleicht können wir uns da ja einen Admin-Account anlegen? Ausgefüllt und abgesendet … uuuund fehlgeschlagen. Immerhin. Doch die Fehlerseite ist sehr detailiert:

Screenshot der Fehlerseite, auf der alle Variablen dargestellt werden u.A. das Datenbank-Passwort

Die Fehlermeldung kommt vom PHP-Framework Laravel, mit dem die API programmiert wurde. Und diese Fehlermeldung zeigt uns alle Konfigurations-Variablen an – also unter anderem die Zugangsdaten zur Datenbank.

🚧 Achtung, Baustelle 🚧

Doch warum sehen wir all das? Nun, Learnu hat ihre Website im Debug-Modus gelassen. Dies ist ein spezieller Modus, der die Entwicklung erleichtert – unter anderem, indem sehr ausführliche Fehlermeldungen angezeigt werden.

Deshalb sollte der Debug-Modus aber auf keinen Fall im normalen Betrieb aktiviert sein. Sonst kann nämlich genau das passieren: Unbefugte bekommen ausführliche Fehlermeldungen zu sehen. Die Laravel-Dokumentation warnt sogar extra davor:

For local development, you should set the APP_DEBUG environment variable to true. In your production environment, this value should always be false. If the variable is set to true in production, you risk exposing sensitive configuration values to your application’s end users.

Doch diese Warnung hat Learnu wohl nicht gesehen oder ignoriert.

🎭

Die auf der Fehlerseite sichtbaren Datenbank-Zugangsdaten brachten uns zum Glück nicht viel, da die Datenbank nicht öffentlich erreichbar ist. Allerdings steht in der Fehlermeldung auch das JWT_SECRET, also ein geheimer Code, mit dem wir unbeschränkt und beliebig Authentifizierungs-Schlüssel generieren können. Mit diesen Schlüsseln können wir uns als jede*r beliebige Nutzer*in ausgeben – und der Server glaubt uns, weil wir ja einen korrekten Authentifizierungs-Schlüssel haben3.

🚨 “Fahrzeugpapiere und API-Dokumentation, bitte.”

Wir können uns jetzt also als jeder beliebige User ausgeben – wir wissen aber noch nicht, wie man mit der Programmierschnittstelle (API) des Servers kommuniziert. Doch nach etwas Raten landen wir auf “https://admin.learnuapp.net/docs”. Dort findet sich eine Dokumentation, wie die API funktioniert. Vermutlich liegt diese dort für die Entwickler*innen, um im Zweifelsfall schnell nachgucken zu können.4 Und auch wir freuen uns, so eine API-Dokumentation zu haben. Denn so haben wir schnell einen Überblick, wie diese funktioniert und können anfangen, die API auszuprobieren.

Unsere erste Anlaufstelle war, wie auch schon bei Scoolio, der Profil-Endpunkt unter https://admin.learnuapp.net/api/users/{USER_ID}/profile. Leider entdeckten wir hier schon das erste Problem, denn die Antwort sieht in etwa so aus (diese hier haben wir uns natürlich ausgedacht):

{
  "status": true,
  "message": "User profile received successfully.",
  "data": {
    "id": 91213,
    "fullname": "Anna Maier",
    "username": "Anna",
    "email": "anna.maier.1312@gmail.com",
    "created_date": "2021-01-12 13:12:13",
    "modified_date": "2021-03-11 13:12:13",
    "biography": "🏳️‍🌈",
    "city_id": "db21a7bf-60bc-4070-89c6-ba1d12605b01",
    "school_id": "ebf94c15-bbc1-4d48-9dae-2452516dc277",
    "avatar": null,
    "subscribers_count": 2,
    "subscriptions_count": 0,
    "is_subscribed": false
  }
}

Die Nutzer*innen-Accounts bei Learnu sind aufsteigend durchnummeriert, wie wir es schon von unzähligen anderen Apps kennen. Aber ist es nicht möglich, beliebig viele Profile auf einmal abzurufen, denn es gibt eine Abruflimitierung von einem Profil pro Sekunde. Allerdings entdecken wir in der Doku auch noch einen Such-Endpunkt, der zu jeder 3-stelligen Buchstabenkombination alle passenden Profile ausgibt. Suchen wir zum Beispiel nach ann (https://admin.learnuapp.net/api/users/search/list?username=ann), sieht die Antwort in etwa so aus:

{
  "status": true,
  "message": "Search list of users received successfully.",
  "data": [
    {
      "id": 91213,
      "fullname": "Anna Maier",
      "username": "Anna",
      "email": "anna.maier.1312@gmail.com",
      "is_premium": 0,
      "is_active": 1,
      "created_date": "2021-01-12 13:12:13",
      "modified_date": "2021-03-11 13:12:13",
      "content_count": 0,
      "forum_count": 0,
      "earnings_count": 0,
      "biography": "🏳️‍🌈",
      "city_id": "db21a7bf-60bc-4070-89c6-ba1d12605b01",
      "school_id": "ebf94c15-bbc1-4d48-9dae-2452516dc277",
      "avatar": null
    },
    {
      "id": 23420,
      "fullname": "Anna-Marie Müller",
      "username": "Anna2",
      "email": "anna.marie.2342@hotmail.com",
      "is_premium": 0,
      "is_active": 1,
      "created_date": "2021-01-12 13:12:13",
      "modified_date": "2021-03-11 13:12:13",
      "content_count": 0,
      "forum_count": 0,
      "earnings_count": 0,
      "biography": "homework is for me? 🥺👉👈",
      "city_id": "db21a7bf-60bc-4070-89c6-ba1d12605b01",
      "school_id": "ebf94c15-bbc1-4d48-9dae-2452516dc277",
      "avatar": null
    },
    
  ]
}

Da wir auch eine Suche pro Sekunde durchführen können, bedeutet das: Um die halbe Million Datensätze herunterzuladen, bräuchten wir nicht 500.000 Sekunden (etwa 6 Tage), sondern nur noch etwa 18.0005 Sekunden, also 5 Stunden.

Open Source wider Willen

Nun können wir alle Profile abrufen und uns als beliebige andere Personen ausgeben, mehr als genug um es zu melden. Also geht das übliche Prozedere los: Wir schreiben unseren Report, um ihn dann an das CERT-Bund beim BSI, den Hersteller und die zuständige Landesdatenschutzbehörde zu schicken.

Doch nachdem die Website bereits schlecht konfiguriert und noch im Entwicklungsmodus ist, haben wir den Verdacht, dass vielleicht noch mehr Dinge falsch konfiguriert sind.

Wir vermuten also, dass vielleicht die .git/index-Datei öffentlich abrufbar sein könnte. Das ist eine interne Datei des Versionskontrollsystems, das für die Entwicklung von Learnu genutzt wurde. Darin stehen alle Dateien, die die Entwickler*innen mit git verwalten, also (fast) alle Quelltextdateien des Learnu-Servers. Das ist eine altbekannte Sicherheitslücke und es empfiehlt sich, den .git/-Ordner nicht öffentlich zugänglich zu machen6.

Für uns ist das sehr nützlich: So wissen wir nämlich, welche anderen Code-Dateien es auf dem Webserver gibt, zum Beispiel die Datei https://admin.learnu.net/config/mail.php. Die können wir natürlich auch abrufen, denn auch dieser Teil war falsch konfiguriert.7 Darin befanden sich Zugangsdaten zu einem Anbieter, über den learnu Mails versendet hat. Da auch der Code mit Konfigurationsdateien definitiv nicht von außen zugänglich sein sollte, müssen wir also den Report nochmal ergänzen und schicken ihn endgültig ab.

Fazit

Obwohl die Datenbank diesmal also nicht aus dem Internet erreichbar war, kamen wir trotzdem an alle Daten. Deshalb: Konfiguriert eure Webserver ordentlich. Gebt nur Dateien aus, die auch ausgegeben werden sollen, also weder PHP-Files noch den .git Ordner noch die Konfiguration des Webservers selbst. Lasst eure Web-Frameworks nicht im Entwicklungsmodus laufen. Und stellt – verdammt nochmal – nur die personenbezogenen Daten über die Programmierschnittstelle bereit, die die App auch wirklich braucht!

Pulling the plug 🔌

Immerhin: Nachdem wir wiederholt nachfragten und teilweise im 15-Minuten-Takt anriefen, ist die Plattform mittlerweile offline – und bleibt das nach Aussage der beiden Gründer auch. Wir beglückwünschen die Gründer von Learnu zu der Erkenntnis, dass es besser ist, die App nicht weiterzubetreiben und zur Entscheidung, die App dauerhaft offline zu lassen. Damit haben sie ihre Datenschutzprobleme konsequenter und dauerhafter gelöst als manch andere.

Doch Fakt ist: Die App ist mit den Daten ihrer Nutzer*innen extrem nachlässig umgegangen. Wenn die Gründer in ihrem Podcast lapidar sagen “Who cares – es war eine super Erfahrung” – müssen wir antworten: Wir caren! Und wir finden es unverantwortlich und frech, mit den Daten von Schüler*innen zu spielen, nur um das Ego zu pushen.

Die Learnu-Gründer*innen hingegen finden ihr Vorgehen sogar so geil, dass sie ein eigenes “Startup Programm für Schüler*innen” namens YoungUp gestartet haben. Dort kann man dann “in verschiedensten Bereichen Erfahrung sammeln und Verantwortung übernehmen.” Wir hätten uns ja gewünscht, dass die beiden Verantwortung für die Daten ihrer Kund*innen übernommen hätten.

Dass es so weit gekommen ist, liegt aber nicht nur an den beiden, sondern ist Ergebnis eines kaputten Systems. Das entschuldigt zwar das Verhalten der beiden nicht, aber so werden auch einige größere Probleme sichtbar:

Liberallala

Das erste Problem ist das gesellschaftliche Drängen auf einen lückenlosen und möglichst aufpolierten Lebenslauf: Abi und Auslandsaufenthalt gelten als erstrebenswert – und am besten noch möglichst früh ein eigenes Startup gründen.

Nicht falsch verstehen: Mit einer guten Idee (und dem entsprechenden fachlichen Background) kann es super cool sein, etwas zu gründen! Dabei kann man viel lernen – und sogar einen positiven Beitrag für die Gesellschaft leisten.

Doch wer sein eigenes “Business” nur um des “Business” und des Lebenslaufs willen gründen will, sollte sich das gut überlegen – vor allem, wenn es dabei um die Daten fremder Leute geht. Denn dann ist das nicht mehr nur eine nette Spielerei, bei der man auch mal Fehler machen darf, um daraus zu lernen – hier geht es um ein echtes Risiko für die betroffenen Nutzer*innen.

Aber ich bin doch klein, ich brauche keinen Datenschutz 🥺

Hier kommen wir zum zweiten Problem: Viele Startups denken, die Spielregeln gelten für sie nicht. Schon scoolio hat uns erzählt, dass sie als Startup nicht die Möglichkeit hätten, sichere Software zu bauen. Dabei ist sichere Software und Datenschutz keine nette Zugabe, die man vielleicht mal einbauen kann, wenn gerade Geld und Zeit übrig sind — und seien wir ehrlich, das sind sie nie. Denn die DSGVO gilt auch für Startups. Wenn ein Produkt reif genug ist, um Kund*innen-Daten speichern zu können, dann muss es auch reif genug sein, diese für sich zu behalten.

Nach uns die Sintflut

Ein drittes Problem: Die Konsequenzen für andere werden ignoriert. Denn wenn die Gründer von ihrem Vorbild Mark Zuckerberg und dessen Ignoranz (“The biggest risk you can take is not taking any risks”) schwärmen, müssen wir fragen, ob das die richtigen Vorbilder sind. Wenn das Vorbild ein Unternehmen ist, das wissentlich Nutzer*innen manipuliert, Hass und Hetze schürt und vieles mehr, dann braucht man sich nicht wundern, wenn ihnen der Schutz der persönlichen Daten anderer egal ist.

Wann immer es um “digitale Innovation” geht, muss klar sein: Nur weil etwas digital ist, ist es nicht automatisch gut. Unsere Gesellschaft muss dringend lernen, neue Apps oder Plattformen nicht blind zu bejubeln – sondern sie zu hinterfragen und zu prüfen. Trotzdem ist auch klar: Diese Aufgabe kann nicht bei den einzelnen Nutzer*innen liegen.

👀

Deshalb ist das vierte Problem: Fehlende Kontrolle durch die zuständigen Stellen.

Durch die DSGVO gibt es eine größtenteils sehr gute Rechtsgrundlage für Datenschutz. Diese muss aber auch durchgesetzt werden. Unsere Zusammenarbeit mit den Datenschutzbehörden ist stets sehr gut gewesen, aber diese fangen oft erst an zu arbeiten, nachdem wir (und andere) von extern die Probleme gefunden und an sie herangetragen haben.

Und um das zu ändern braucht es mehr Leute, Ressourcen und Kompetenzen in den Datenschutzbehörden. Diese müssen endlich auch von sich aus nach solchen Problemen Ausschau halten und bei Verstößen empfindliche Strafen verhängen können.

Timeline

  • 2021-10-18 – Meldung an CERT-Bund, LDI NRW und Learnu
  • 2021-10-20 – Aufgrund ausgebliebener Reaktion: nochmalige Nachfrage des CERT-Bund bei Learnu
  • 2021-10-20 ab 12:30 Uhr – mehrere Anrufversuche bei Learnu
  • 2021-10-20 13:53 Uhr – Learnu bestätigt die gemeldeten Lücken zu überprüfen
  • 2021-10-20 ~23 Uhr – bei routinemäßiger Überprüfung stellen wir fest, dass Learnu offline ist
  • 2021-10-29 – Nachfrage des CERT-Bund bei uns ob die Lücken geschlossen wurden, da keine weitere Reaktion von Learnu
  • 2021-10-29 15:09 Uhr – letzte Fristsetzung für Learnu unsererseits, zum 03.11.2021 12:00 UTC
  • 2021-10-29 – Telefonat mit Learnu, Bestätigung dass Learnu offline bleibt
  • 2021-11-11 – Veröffentlichung dieses Blogposts

Wenn ihr zerforschung unterstützen wollt, findet ihr hier Möglichkeiten: https://zerforschung.org/unterstuetzen/

Titelbild basierend auf “France in XXI Century - Future School” aus der Bilderreihe En L’An 2000.

Das Bild enthält folgende Creative Commons lizensierten Werke:


  1. Der Name setzt sich laut den Gründer*innen aus den Begriffen “lernen” (“learn”), “verdienen” (“earn”) und “du” (“u”) zusammen. 🤡 ↩︎

  2. Liste der standardmäßig vertrauten Zertifizierungsstellen in Windows und iOS / macOS ↩︎

  3. Die Authentifizierungs-Schlüssel hier sind natürlich JSON-Web-Tokens, die wir jetzt mit dem Secret signieren und validieren können. ↩︎

  4. Das ist auch eigentlich gar kein Problem, wenn die API ausreichend geschützt ist. Und wenn die API nicht genug geschützt ist, dann ist Security by Obscurity auch keine Lösung. ↩︎

  5. 26 hoch 3 = 17576 ↩︎

  6. https://en.internetwache.org/dont-publicly-expose-git-or-how-we-downloaded-your-websites-sourcecode-an-analysis-of-alexas-1m-28-07-2015/ ↩︎

  7. PHP ist eine interpretierte Sprache, das bedeutet, dass der Server beim Abruf der Seite den Code ausführt und nur das Ergebnis an den Browser liefert. Dafür muss der Webserver allerdings so konfiguriert sein, dass .php Dateien an den PHP-Interpreter übergeben werden statt sie direkt auszuliefern. ↩︎