LNbits-Vorfall vom 8. November 2026 – technische Analyse
Wie die Wallets geleert wurden – aufbereitet, damit andere Betreiber:innen und das LNbits-Projekt daraus lernen können.
Betroffene Software: LNbits v1.6.2 (self-hosted). Stand der Analyse: 10. November 2026. Alle Zeiten in UTC.
Kurzfassung (TL;DR)
- Ein:e Angreifer:in brauchte keinen Server- oder Admin-Zugang. Ein ganz normaler Nutzer-Account – wie ihn eine Multi-User-Instanz regulär und gewollt vergibt – genügte, um über die Lücken an fremde Guthaben zu gelangen.
- Verkettet wurden drei Schwächen: eine IDOR in der TPoS-Extension, ein Local-File-Read/SSRF in der Paywall-Extension und die aktivierte Login-Methode
user-id-only(die User-ID ist das Passwort). - Ergebnis: Übernahme des Superusers, Auslesen aller Admin-Keys, systematisches Leeren der Wallets.
- Schaden: 864 Auszahlungen; der Grossteil der entwendeten Sats floss an einen Ziel-Node. Die genaue Gesamtsumme wird hier bewusst nicht genannt.
- Der Lightning-Node (LND) selbst wurde nicht direkt missbraucht – jede LND-Zahlung hat einen passenden LNbits-Eintrag, keine Kanal-Schliessung, kein On-Chain-Abfluss. Die mitgemountete
admin.macaroonwar aber lesbar und gilt als kompromittiert.
user-id-only als Credential
5. Verschärfende Konfiguration
6. Zeitstrahl
7. Indicators of Compromise
8. Bin ich betroffen? – Detektion
9. Gegenmassnahmen & Härtung
1. Die Angriffskette im Überblick
Ausgangspunkt ist ein ganz normaler Nutzer-Account, wie ihn eine Multi-User-Instanz regulär und gewollt vergibt – also kein privilegierter Zugang. Von dort greifen die drei Fehler ineinander; jeder für sich ist schon schlimm, zusammen ergeben sie die Totalübernahme:
- Schwachstelle A (TPoS): Mit einem eigenen „TPoS“-Terminal lässt sich per Update die Wallet-Zuordnung auf ein fremdes Wallet umbiegen und dieses dann über die ATM-/Withdraw-Funktion leeren. So wurde zuerst das Service-Fee-Wallet (es gehört dem Superuser) angezapft.
- Schwachstelle B (Paywall): Eine Paywall vom Typ
filelädt eine frei wählbare URL perurllib.file://wird akzeptiert → beliebige lokale Dateien lesbar, u. a. die LNbits-Datenbank (alle Keys), die Datei.super_userund die gemountete LND-Macaroon. - Schwachstelle C (Auth): Mit der ausgelesenen Superuser-ID genügt ein
user-id-only-Login, um Admin zu werden. - Plünderung: Als Superuser alle Nutzer auflisten, deren Admin-Keys abrufen und per
POST /api/v1/paymentsjedes Wallet an den eigenen Node ausschütten.
2. Schwachstelle A — TPoS IDOR (Wallet-Übernahme)
IDOR / Broken Access Control Extension tpos, Endpoint PUT /tpos/api/v1/tposs/{tpos_id}.
Das Datenmodell des Update-Requests enthält ein frei setzbares Feld wallet:
class CreateTposData(BaseModel):
wallet: str | None # <- vom Client bestimmbar
name: str | None
...
Der Handler prüft die Besitzrechte nur gegen den aktuellen Stand und schreibt danach alle gelieferten Felder ungefiltert per setattr zurück:
@tpos_api_router.put("/api/v1/tposs/{tpos_id}")
async def api_tpos_update(data, tpos_id,
wallet = Depends(require_admin_key)):
tpos = await get_tpos(tpos_id)
...
if wallet.wallet.id != tpos.wallet: # Besitzprüfung
raise HTTPException(403, "Not your TPoS.")
...
for field, value in update_payload.items():
setattr(tpos, field, value) # <- 'wallet' wird überschrieben
tpos = await update_tpos(tpos)
Der Denkfehler: Die Prüfung bestätigt nur, dass das TPoS jetzt dem Aufrufer gehört. Unmittelbar danach darf derselbe Request die wallet-Zuordnung auf ein beliebiges fremdes Wallet setzen. Es gibt keine zweite Prüfung, ob das neue Ziel-Wallet dem Aufrufer gehört.
Ablauf (konzeptionell)
- Eigenes TPoS anlegen →
wallet= eigenes Wallet (Besitzprüfung besteht). PUT …/tposs/{id}mitwallet= fremde Wallet-ID → Zuordnung umgebogen.- ATM-/Withdraw-Funktion des TPoS auslösen. Das System zieht jetzt vom fremden Wallet. In den Logs verrät sich das durch Fehlermeldungen wie
Insufficient balance. Your balance is 398313 sats– obwohl das eigene Wallet 0 sat hatte.
Voraussetzung ist die Kenntnis einer fremden Wallet-ID. Für das Service-Fee-Wallet lässt sie sich indirekt gewinnen, weil Service-Fee-Buchungen denselben payment_hash wie die auslösende Zahlung tragen. Auf diesem Server wurden so 47 Angreifer-TPoS mit withdraw_limit = 100000000 auf das Fee-Wallet gelegt.
3. Schwachstelle B — Paywall Local-File-Read / SSRF
LFI / SSRF Extension paywall (v1.2.0), Download-Endpoint der Paywall.
Eine Paywall vom Typ file speichert in file_config.url eine vom Nutzer gesetzte URL. Beim Download wird diese ungefiltert an urllib übergeben:
async def _file_streamer(url, headers):
with request.urlopen(request.Request(url=url, headers=headers)) as dl_file:
yield dl_file.read()
Das Problem: urllib.request.urlopen akzeptiert nicht nur http(s)://, sondern auch file://. Es gibt keine Schema-Allowlist und keine Pfad-Einschränkung. Damit wird der Endpoint zu:
- Local File Read über
file:///…– beliebige für den Container lesbare Dateien. - SSRF über
http://…– Requests aus Sicht des Servers (interne Dienste, Cloud-Metadaten etc.).
Verschärfend: Der LNbits-Container lief als root und hatte das Datenverzeichnis sowie das LND-Verzeichnis gemountet. Lesbar waren damit u. a.:
- die LNbits-SQLite-Datenbank → alle Wallet-Admin-Keys und Account-Daten,
- die Datei
.super_user→ die Superuser-ID, - die (read-only) gemountete
admin.macaroondes Lightning-Nodes.
Der Download setzt eine bezahlte Rechnung voraus – der Betrag ist aber frei und darf 0 sein, die Hürde entfällt also. In den Logs taucht als zuletzt gespeicherte Sonde der Wert file:///data/.env auf. Welche Dateien konkret ausgelesen wurden, ist aus den Logs nicht beweisbar; der unmittelbar folgende Superuser-Login (siehe Schwachstelle C) ist aber genau damit konsistent.
Hinweis: Eine zweite Extension (fossa) erlaubt beim Anlegen ebenfalls ein fremdes Wallet (IDOR). Darüber floss nachweislich kein Geld ab, der Fehler sollte aber mitbehoben werden.
4. Schwachstelle C — user-id-only als Credential
Design / Auth Kern-Einstellung auth_allowed_methods.
War die Methode user-id-only aktiv, genügt die Kenntnis einer User-ID, um sich als diese:r Nutzer:in einzuloggen (…/api/v1/auth?usr=<id>). Die User-ID ist damit faktisch Benutzername und Passwort in einem. Kombiniert mit dem Local-File-Read aus Schwachstelle B (Auslesen von .super_user) führt das direkt zu Admin-Rechten: Danach lieferte /admin/api/v1/settings Status 200, und die Angreiferseite listete via /users/api/v1/user?limit=100&offset=… sämtliche Konten samt Wallets und Admin-Keys auf.
5. Verschärfende Konfiguration
Diese Punkte haben den Schaden erst gross gemacht – unabhängig von den Code-Bugs:
- Öffentliche Multi-User-Instanz: Der Betrieb für fremde Nutzer:innen ist ein legitimer, gewollter Einsatzzweck von LNbits – er bedeutet aber, dass jede authentifizierte Route als potenziell feindlich gelten muss (Defense in Depth). Genau hier versagten die Extensions.
user-id-onlyinauth_allowed_methods: macht aus einer ausgelesenen ID sofort einen gültigen Login.- Service-Fee-Wallet gehört dem Superuser: ein attraktives, zentrales Ziel.
- Container als root, LND-Macaroon gemountet: hebt einen Datei-Read zum Node-Risiko.
- Audit-Log nur für 4xx/5xx, ohne IP: die Rekonstruktion war nur über das allgemeine Debug-Log möglich.
6. Zeitstrahl (UTC)
| Zeit | Ereignis |
|---|---|
| 07.11. abends | Erste Erkundung: Tests von /api/v1/auth/impersonate, /admin/…, Testregistrierung. |
| 08.11. 09:42–09:49 | Mehrere Wegwerf-Accounts (u. a. „admin“). Automatisiertes Abklopfen der Extensions. |
| 08.11. 09:48 | Fossa-IDOR auf fremdes Wallet getestet (vorher mit deadbeef… auf Prüfung getestet). |
| 08.11. 10:33 | Phase 1 – TPoS-IDOR: Fee-Wallet wird über umgebogene TPoS geleert. |
| 08.11. 11:52–12:27 | Paywall-LFI: Paywall Typ file, file:///…, mehrfacher Download. |
| 08.11. 12:02 | Erster usr=-Login. |
| 08.11. 12:31 | Superuser bestätigt (/admin/api/v1/settings → 200), Massen-Enumeration aller Nutzer. |
| 08.11. 13:11–16:22 | Phase 2: systematisches Leeren der Wallets, ~alle 5 s eine Zahlung. |
| 08.11. 14:13–14:16 | Versuche über die Node-UI (phoenixd/send, DELETE /node/…/channels) – alle gescheitert. |
| 08.11. 16:22 | LNbits gestoppt → Angriff endet. Nach Neustart keine Angreifer-Aktivität mehr. |
7. Indicators of Compromise (IoC)
Angreifer-bezogene Artefakte. Die IP-Adressen stammen aus dem Applikations-Log; da der Reverse-Proxy
so konfiguriert war, dass er jedem X-Forwarded-For vertraute, können sie gefälscht sein und sind nur als
Indiz zu verstehen.
Ziel-Node der gestohlenen Sats
021294fff596e497ad2902cd5f19673e9020953d90625d68c22e91b51a45c032d3
Nahezu alle entwendeten Sats gingen an diesen einen Node. Betreiber von Verwahr-/Börsendiensten können diesen Pubkey prüfen.
Beobachtete Quell-IPs (mit Spoofing-Vorbehalt)
66.179.142.14 64.38.116.4 45.86.210.167
186.247.35.215 186.247.40.143 216.203.38.19
94.183.234.112 104.28.0.0/16 (WARP-Bereich)
Angreifer-Artefakte in den Extension-Daten
- ~47 TPoS mit Zufallsnamen (z. B.
awu421m8,atmfjyiks) undwithdraw_limit = 100000000auf einem fremden Wallet. - Paywall vom Typ
filemitfile_config.url = file:///…(z. B.file:///data/.env). - Kurz hintereinander angelegte Wegwerf-Accounts, u. a. Benutzername
admin.
8. Bin ich betroffen? — Detektion
Zur Prüfung einer eigenen Instanz (vorher den Dienst stoppen und auf Kopien arbeiten):
Log-Signaturen
# verdächtige Wallet-Übernahme per TPoS-Update, gefolgt von „fremdem“ Saldo
grep -nE 'PUT /tpos/api/v1/tposs/' debug.log
grep -n 'Insufficient balance. Your balance is' debug.log
# Paywall-Dateidownloads (LFI-Sonden)
grep -nE 'paywall/(download|api/v1/paywalls)' debug.log
# Login per User-ID + Admin-Zugriff
grep -nE '/api/v1/auth\?usr=|/admin/api/v1/settings|/users/api/v1/user' debug.log
In der Datenbank
- TPoS und Fossa-Einträge, deren
walletnicht zum anlegenden Account passt. - Paywalls mit
extras.type = "file"und einerfile_config.url, die mitfile:oder einer internen Adresse beginnt. - Auffällige Serien ausgehender Zahlungen (gleiche Beträge, ~5-Sekunden-Takt) an denselben Ziel-Node.
9. Gegenmassnahmen & Härtung
Sofort
- LNbits abschalten, bis gepatchte Extensions/Version eingespielt sind, und nicht öffentlich erreichbar betreiben.
- Falls eine LND-Macaroon im Container lesbar war: Macaroons erneuern und alle verbundenen Dienste neu anbinden. Die alte
admin.macaroonals kompromittiert behandeln. - Alle LNbits-Keys als kompromittiert ansehen: Wallet-Admin-/Invoice-Keys rotieren, Superuser neu aufsetzen, Angreifer-Accounts sperren, Angreifer-Objekte (TPoS/Fossa/Paywall) löschen.
Konfiguration
user-id-onlyausauth_allowed_methodsentfernen.- Öffentliche Erreichbarkeit und offene Registrierung bewusst abwägen – wo kein Multi-User-Betrieb nötig ist, die Instanz intern/authentifiziert halten. (Multi-User-Hosting bleibt legitim; dann zählt Defense in Depth.)
- Service-Fee-Wallet vom Superuser-Account trennen.
- Nicht benötigte Extensions deaktivieren – insbesondere
tpos,paywallundfossa, bis behoben. - Container nicht als root laufen lassen; Mounts (vor allem Node-Macaroons) auf das Nötigste und read-only beschränken.
- Audit-Logging vollständig aktivieren (auch 2xx, inkl. Quell-IP), und den Reverse-Proxy nicht blind jedem
X-Forwarded-Forvertrauen lassen.
Fix-Empfehlung an die Extension-Autor:innen
- TPoS: Beim Update das neue Ziel-Wallet gegen den Besitzer prüfen – oder das Feld
walletaus dem Update-Modell ganz ausschliessen (walletunveränderlich). - Paywall: In
urlopeneine Schema-Allowlist (https, ggf.http) erzwingen,file://und interne Ziele blocken, und den Fetch nicht mit Dateisystemzugriff des App-Users ausführen. - Fossa: Beim Anlegen prüfen, dass das angegebene Wallet dem Aufrufer gehört.
Dieser Bericht wurde bewusst anonymisiert: interne Adressen, Hostnamen, Wallet-IDs und Konten der betroffenen Instanz sind entfernt oder durch Platzhalter ersetzt. Angreifer-bezogene Artefakte sind erhalten, damit sie zur Abwehr und Zuordnung dienen können.