Disclosing unauthenticated user endpoint in outdoor activities app

Security

A machine-translated German version is available below.

Late one night in February I was planning some weekend trips around Freiburg. The Badische Zeitung has an excellent app called “BZ-Lieblingsplätze”. It shows lovely places nearby that are worth visiting. The app does not have a web view that I knew of, so I wanted to check which API was running in the background of the app. This led to the discovery of an API endpoint that allowed the retrieval of the whole user database, including names, emails and encoded transaction metadata concerning app store purchases.

Reverse engineering the app

On the 26th I met with a developer to discuss technical details about the app including how to actually discover which API endpoints exist and how to authenticate. It turned out that the app was actually developed by a third-party developer, so BZ.medien also had to do some reverse engineering to confirm the vulnerability. That really proves that reverse engineering is also valuable outside of maliciously attacking systems. Source code and documentation get lost over time. Companies are unable to understand their own apps because they sub-contracted the development to third parties.

In my case I started by downloading the APK and unpacking it. At that stage it was obvious that the app was a Flutter app. Flutter bundles the application code in a native binary. While this is not obfuscation on purpose, it makes it very hard to reverse engineer apps. There is a reverse engineering framework for Flutter called reFlutter. I did not know at the time, but I want to check this out in the future.

So if static analysis does not work this leaves us with the following options:

  1. Use Frida to perform dynamic analysis on an app or OS level
  2. Make the app use a TLS certificate we got the private key for and MITM the connection between the app and the API
  3. Figure out the API endpoint statically or by listening for DNS traffic and guessing the API endpoints.

I started with the easiest option, option 3. I had no luck guessing the API endpoints. Taking a step back and using the now known domain of the API and searching through strings in the Flutter binary was also without success.

Continuing with option 2, I was able to record TLS traffic and decrypt it. The app was able to run on a regular Android emulator. I used the “Google API” flavored ones. The non-Google, AOSP ones do not have any Google Play services so you might run into crashes if the app depends on those. The full Google Play images do not allow debugging. The Google API ones are kind of a middle ground. Banking apps most likely won’t work on these, but regular apps should – and it’s possible to install system-wide TLS certificates.

Discovering the API

After installing the app using adb install-multiple and setting up HTTP-Toolkit using a system TLS certificate, I was able to see how the API worked. The API showed that the app would fetch my user information via an API endpoint like GET /users/123456. Being curious, I removed my ID and expected to be denied access. However, the request took a few seconds and returned 10 megabytes of data on ~19k users.

The data included names, email addresses, dates about the validity of the subscription, and for users who bought the subscription via Google Play or Apple App Store, encoded transaction data. Note, that the transaction data does not contain any information about bank details. The transaction is a JWSTransactionDecodedPayload.

None of the user data was persisted while testing for the vulnerability.

Conclusion

Looking at the timeline below the most annoying thing was the long waiting time between sending out the disclosure and first hearing back. Ideally, a short answer should be sent back saying that the vulnerability is being looked at if it is forwarded internally.

From my perspective it looked like the disclosure landed in their spam folder or was regarded as begbounty. I was already considering sending a letter via snail mail or visiting their offices in person. BZ.medien confirmed that the app did not land in the spam folder, but internal processed delayed a first answer.

In reality, BZ.medien did a great job of mitigating this vulnerability in less than 24 hours, which is exceptional.

Disclosure Timeline

  • 19 February 2026: Disclosure sent to BZ.
  • 20 February 2026: Vulnerability fixed.
  • 26 February 2026: First response by BZ.medien, confirming the vulnerability was fixed.
  • 27 February 2026: Meeting with BZ.medien developer to discuss technical details of the vulnerability.
  • 31 March 2026: Blog post sent to BZ.medien for review, also asking whether this was reported to regulatory bodies.
  • 12 April 2026: Publication of this blog post.



Eines späten Abends im Februar plante ich einige Wochenendausflüge in der Umgebung von Freiburg. Die Badische Zeitung hat eine hervorragende App namens „BZ-Lieblingsplätze“. Sie zeigt schöne Orte in der Nähe, die einen Besuch wert sind. Die App hat meines Wissens nach keine Webansicht, daher wollte ich überprüfen, welche API im Hintergrund der App läuft. Dies führte zur Entdeckung eines API-Endpunkts, über den die gesamte Benutzerdatenbank abgerufen werden konnte, einschließlich Namen, E-Mail-Adressen und verschlüsselter Transaktionsmetadaten zu App-Store-Käufen.

Reverse Engineering der App

Am 26. traf ich mich mit einem Entwickler, um technische Details der App zu besprechen, darunter auch, wie man konkret herausfindet, welche API-Endpunkte existieren, und wie die Authentifizierung funktioniert. Es stellte sich heraus, dass die App tatsächlich von einem externen Entwickler erstellt worden war, sodass BZ.medien ebenfalls Reverse Engineering betreiben musste, um die Sicherheitslücke zu bestätigen. Das beweist wirklich, dass Reverse Engineering auch außerhalb böswilliger Angriffe auf Systeme von großem Wert ist. Quellcode und Dokumentation gehen mit der Zeit verloren. Unternehmen sind nicht in der Lage, ihre eigenen Apps zu verstehen, weil sie die Entwicklung an Dritte ausgelagert haben.

In meinem Fall habe ich zunächst die APK heruntergeladen und entpackt. Zu diesem Zeitpunkt war offensichtlich, dass es sich um eine Flutter-App handelte. Flutter bündelt den Anwendungscode in einer nativen Binärdatei. Das ist zwar keine absichtliche Verschleierung, macht es aber sehr schwer, Apps zurückzuentwickeln. Es gibt ein Reverse-Engineering-Framework für Flutter namens reFlutter. Das wusste ich damals noch nicht, aber ich möchte mir das in Zukunft einmal ansehen.

Wenn also die statische Analyse nicht funktioniert, bleiben uns folgende Optionen:

  1. Mit Frida eine dynamische Analyse auf App- oder Betriebssystemebene durchführen
  2. Die App dazu bringen, ein TLS-Zertifikat zu verwenden, für das wir den privaten Schlüssel haben, und die Verbindung zwischen der App und der API per MITM abfangen
  3. Den API-Endpunkt statisch ermitteln oder durch Abhören des DNS-Verkehrs und Erraten der API-Endpunkte

Ich begann mit der einfachsten Option, Option 3. Beim Erraten der API-Endpunkte hatte ich kein Glück. Auch der Versuch, einen Schritt zurückzutreten, die nun bekannte Domain der API zu nutzen und die Zeichenfolgen in der Flutter-Binärdatei zu durchsuchen, blieb erfolglos.

Als ich mit Option 2 fortfuhr, gelang es mir, den TLS-Datenverkehr aufzuzeichnen und zu entschlüsseln. Die App ließ sich auf einem normalen Android-Emulator ausführen. Ich verwendete die Varianten mit „Google API“. Die Nicht-Google-Versionen (AOSP) verfügen über keine Google Play-Dienste, sodass es zu Abstürzen kommen kann, wenn die App darauf angewiesen ist. Die vollständigen Google Play-Images lassen kein Debugging zu. Die Google-API-Versionen sind so etwas wie ein Mittelweg. Banking-Apps werden darauf höchstwahrscheinlich nicht funktionieren, aber normale Apps sollten es – und es ist möglich, systemweite TLS-Zertifikate zu installieren.

Die API erkunden

Nachdem ich die App mit adb install-multiple installiert und das HTTP-Toolkit mit einem System-TLS-Zertifikat eingerichtet hatte, konnte ich mir ein Bild davon machen, wie die API funktioniert. Die API zeigte, dass die App meine Benutzerdaten über einen API-Endpunkt wie GET /users/123456 abrufen würde. Aus Neugierde entfernte ich meine ID und erwartete, dass mir der Zugriff verweigert würde. Die Anfrage dauerte jedoch einige Sekunden und lieferte 10 Megabyte an Daten zu etwa 19.000 Nutzern.

Die Daten umfassten Namen, E-Mail-Adressen, Angaben zur Gültigkeit des Abonnements sowie – für Nutzer, die das Abonnement über Google Play oder den Apple App Store erworben hatten – verschlüsselte Transaktionsdaten. Beachten Sie, dass die Transaktionsdaten keine Informationen zu Bankdaten enthalten. Die Transaktion ist ein JWSTransactionDecodedPayload.

Während des Tests auf die Sicherheitslücke wurden keine der Benutzerdaten gespeichert.

Fazit

Wenn man sich die untenstehende Zeitleiste ansieht, war das Ärgerlichste die lange Wartezeit zwischen dem Absenden der Meldung und der ersten Rückmeldung. Im Idealfall sollte eine kurze Antwort zurückgeschickt werden, in der mitgeteilt wird, dass die Sicherheitslücke geprüft wird, falls sie intern weitergeleitet wurde.

Aus meiner Sicht sah es so aus, als wäre die Offenlegung in ihrem Spam-Ordner gelandet oder als „Begbounty“ eingestuft worden. Ich hatte bereits darüber nachgedacht, einen Brief per Post zu schicken oder persönlich in ihren Büros vorbeizuschauen. BZ.medien bestätigte, dass die Offenlegung nicht im Spam-Ordner gelandet war, sondern dass interne Abläufe eine erste Antwort verzögert hatten.

Tatsächlich hat BZ.medien diese Schwachstelle in weniger als 24 Stunden hervorragend behoben, was außergewöhnlich ist.

Zeitachse der Offenlegung

    1. Februar 2026: Offenlegung an BZ gesendet.
    1. Februar 2026: Schwachstelle behoben.
    1. Februar 2026: Erste Antwort von BZ.medien, in der bestätigt wurde, dass die Schwachstelle behoben wurde.
    1. Februar 2026: Treffen mit einem Entwickler von BZ.medien, um technische Details der Schwachstelle zu besprechen.
    1. März 2026: Blogbeitrag zur Überprüfung an BZ.medien gesendet, mit der Frage, ob dies den Aufsichtsbehörden gemeldet wurde.
    1. April 2026: Veröffentlichung dieses Blogbeitrags.

Do you have questions? Send an email to max@maxammann.org