LNbits Attack - My story

How an LNbits server was used to drain a Lightning node

Summary

An attacker went from a free LNbits account to full control of the
Lightning node behind it in about eight hours. They abused LNbits’
own features and one extension bug to install a malicious extension,
which read the node’s LND admin macaroon and tried to close every
channel to the attacker’s address and sweep the on-chain wallet.
Fast force-closes by the operator limited the loss to a single channel.

Setup that was attacked

  • LNbits 1.5.4 in Docker, exposed to the internet through a Cloudflare Tunnel
  • Funding source: LND REST on the local network
  • The container mounted the node’s whole LND folder, including admin.macaroon
  • Open sign-up and “login by user ID” were both enabled

Attack chain

  1. Accounts. The attacker registered dozens of accounts, many with
    admin-style usernames (admin, root, lnbitsadmin, operator, the
    operator’s own handles) to find real ones, and sent thousands of
    password guesses. None got into an admin account, and nothing
    rate-limited them.
  2. Probing extensions. They enabled every available extension on
    their own accounts and tested each for a way in, including making
    the server send requests to its own internal addresses (SSRF).
  3. Reading server files. A flaw in the Paywall extension’s
    “file” download let a normal user read files inside the LNbits
    container, unlocked with a 1-sat payment.
  4. Super user. With what that file read exposed, they made
    authenticated super-user requests, even though LNbits blocks
    super-user login by user ID alone.
  5. Hosting the payload on the victim. As a normal user they uploaded
    a malicious extension zip (labelled as an image) and a fake extension
    catalog as LNbits “assets”.
  6. Installing it. Using super-user settings, they added the asset’s
    local URL as a trusted extension catalog and installed the extension
    from it.
  7. Taking the node. The extension ran as soon as it was installed:
    • read admin.macaroon from the mounted LND folder
    • listed channels and requested co-op closes to the attacker’s address
    • started a loop that tried every 3 minutes to sweep the whole
      on-chain balance
    • opened a remote shell endpoint, which the attacker used to fix
      bugs in their own sweeper and drop helper scripts

LND rejected the attacker’s force-close requests, and their first
sweeper used the wrong API endpoint. The patched sweeper only sent
confirmed funds, and none were confirmed before the container was
stopped.

Signs of compromise to look for

  • An unknown extension in data/extensions/ or data/upgrades/
    (this one was named pwnNNNN)
  • lnbits_extensions_manifests containing a URL pointing at
    127.0.0.1 or /api/v1/assets/
  • Assets with mismatched types (a .zip or .json stored as image/png)
  • Bursts of new accounts with admin-like names, or mass failed logins
  • Paywalls of type “file” you didn’t create
  • Python scripts or JSON logs you don’t recognise in the data folder
  • Lots of 1-sat test payments with odd memos
  • Forwarded IPs in reserved ranges (198.51.100.x, 203.0.113.x):
    the attacker was forging proxy headers

Why it worked

  1. Public LNbits with open sign-up and login by user ID
  2. An extension bug that let a normal user read files on the server
  3. LNbits’ signing secrets stored where that bug could read them
  4. Super users can install code from any catalog (by design)
  5. LNbits held a full-permission admin macaroon. That one fact turned
    an LNbits break-in into a node takeover.
  6. No login rate limiting

If you run LNbits, do this now

  • Turn off open sign-up and “user-id-only” login if you don’t need them
  • Turn off extensions you don’t use, especially ones that fetch URLs
    (Paywall file downloads, Watchonly)
  • Never give LNbits admin.macaroon. Bake one with only the permissions
    it needs (invoices and Lightning payments; no channel closing, no
    on-chain sends) and mount that file alone, not the whole LND folder.
  • Put /admin behind an access gate (Cloudflare Access, VPN, IP allowlist)
  • Rate-limit /api/v1/auth at the proxy
  • Check installed extensions and the manifest list for anything you
    didn’t add
  • Keep LNbits and its extensions up to date

If you think you’re compromised

  1. Stop LNbits and set its restart policy to “no”. Malicious extensions
    run as soon as they load, and LNbits may reinstall a deleted one at
    startup from its database.
  2. Copy the data folder and container logs before changing anything.
  3. On LND: delete the macaroon root keys and generate new macaroons, so
    stolen ones stop working.
  4. Force-close channels you don’t trust, and move all funds to a new seed.
  5. Don’t run the old LNbits install again. Start fresh with a new data
    folder and new secrets.
  6. Pay back any custodial balances you hold for other users.
Write a comment