webhook_url is unvalidated: server-side POST with attacker-chosen headers to any host (SSRF) #2

Open
opened 2026-10-09 17:12:11 +00:00 by padreug · 0 comments
Owner

api_link_create_or_update requires success_url to start with https:// (views_api.py:155-163) but applies no check at all to webhook_url. On every settled payment send_webhook (tasks.py:56-82) POSTs to that URL with headers=json.loads(pay_link.webhook_headers), both creator-controlled. The transport RPCs (transport_rpcs.py:44-51, :86-105) expose the same fields. On a multi-tenant instance with open signup (aio-demo), any account holder with their own wallet admin key can aim the lnbits process at http://169.254.169.254/, http://localhost:<port>/ or anything on the docker network (nostrrelay, bunker, LND REST) and trigger the request by paying their own link. lnbits core has no SSRF helper to reuse.

Fix direction: on create/update (HTTP and transport, via a shared validator) require https:// for webhook_url, resolve the host and reject loopback, link-local, private and metadata ranges; consider an operator-level egress allow-list. Test: webhook_url = "http://169.254.169.254/" and "http://localhost:5000/" are rejected at create time.

Found during reforge run #1 (sandbox lnurlp#5).

`api_link_create_or_update` requires `success_url` to start with `https://` (`views_api.py:155-163`) but applies no check at all to `webhook_url`. On every settled payment `send_webhook` (`tasks.py:56-82`) POSTs to that URL with `headers=json.loads(pay_link.webhook_headers)`, both creator-controlled. The transport RPCs (`transport_rpcs.py:44-51`, `:86-105`) expose the same fields. On a multi-tenant instance with open signup (aio-demo), any account holder with their own wallet admin key can aim the lnbits process at `http://169.254.169.254/`, `http://localhost:<port>/` or anything on the docker network (nostrrelay, bunker, LND REST) and trigger the request by paying their own link. lnbits core has no SSRF helper to reuse. Fix direction: on create/update (HTTP and transport, via a shared validator) require `https://` for `webhook_url`, resolve the host and reject loopback, link-local, private and metadata ranges; consider an operator-level egress allow-list. Test: `webhook_url = "http://169.254.169.254/"` and `"http://localhost:5000/"` are rejected at create time. Found during reforge run #1 (sandbox lnurlp#5).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/lnurlp#2
No description provided.