Описание
Dozzle: Pre-auth SSRF with response-body reflection via POST /api/notifications/test-webhook (default no-auth deploy)
Summary
In a default dozzle deploy (the documented quickstart, no DOZZLE_AUTH_PROVIDER set), POST /api/notifications/test-webhook is reachable without authentication and forwards an attacker-controlled URL into a WebhookDispatcher that:
- Sends an HTTP POST to the supplied URL with attacker-controlled request headers, and
- Returns the response status code AND up to 1MB of the response body to the caller, when the target replies non-2xx.
This is a classic full-reflection SSRF, pre-auth, against any IP/port that dozzle's host can route to — including private subnets, link-local cloud metadata, and loopback services.
Affected versions
internal/notification/dispatcher/webhook.go and internal/web/notifications.go at commit 581bab3a43ead84ea4d009a469a17af98fb3377f and earlier (the test-webhook handler has been in place since the notifications subsystem was added).
Default-deploy reachability chain
So the default Quickstart deploy
exposes POST /api/notifications/test-webhook to the network without any authentication.
The vulnerable handler
input.URL and input.Headers are entirely user-controlled. No host/IP/scheme validation anywhere.
The reflection sink
When the SSRF target returns non-2xx, up to 1 MB of response body becomes part of Error, which is then JSON-encoded back to the attacker.
PoC
A. Read intranet admin-panel response bodies (most common path)
Most internal admin UIs respond to anonymous POST with 401/403 + an HTML or JSON body that contains version banners, CSRF tokens, internal hostnames, etc.
Response shape (writeJSON to the public Internet):
B. Cloud IMDS reachability probe
If StatusCode == 200, IMDS is reachable. For AWS IMDSv2 the unauth POST returns 401 + body which IS reflected.
C. Header injection downstream
Suggested fix
- Refuse
test-webhookwhenAuthorization.Provider == NONE. This is an admin-configuration helper; it should not be reachable on a deploy that has no concept of admin. - SSRF-harden
WebhookDispatcher. Resolve URL host once vianet.LookupIP; refuse private/loopback/link-local/CGNAT; pinhttp.Transport.DialContextto the resolved IP (closes DNS-rebinding TOCTOU). Refuse non-http(s) schemes. - Stop reflecting response body. UX for "test webhook" only needs
Success: bool, StatusCode: int.
Severity
- CVSS 3.1: High —
AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N≈ 7.5 in default no-auth deploy. - Auth: none in default deploy. With
DOZZLE_AUTH_PROVIDER=simpleconfigured, the same primitive is post-auth.
Reproduction environment
- Tested against:
amir20/dozzle:8.xDocker image (commit581bab3a43ead84ea4d009a469a17af98fb3377f). - Code locations:
- Handler:
internal/web/notifications.go:652-716 - Sink:
internal/notification/dispatcher/webhook.go:88-120 - Auth gate:
internal/web/routes.go:130-138, 172-188 - Default provider:
internal/support/cli/args.go:18,main.go:231
- Handler:
Reporter
Eddie Ran. Filed via reporter API per dozzle's SECURITY.md.
Пакеты
github.com/amir20/dozzle
<= 8.14.12
Отсутствует
Связанные уязвимости
Dozzle is a realtime log viewer for docker containers. Prior to 10.5.2, in a default dozzle deploy (the documented quickstart, no DOZZLE_AUTH_PROVIDER set), POST /api/notifications/test-webhook is reachable without authentication and forwards an attacker-controlled URL into a WebhookDispatcher that sends an HTTP POST to the supplied URL with attacker-controlled request headers, and returns the response status code AND up to 1MB of the response body to the caller, when the target replies non-2xx. This vulnerability is fixed in 10.5.2.