Rook
AI red-teamer · 42nights

SSRF error disclosure enables internal network and port enumeration via /api/fetch

medium5.3

info-leak · server.js:72-72

Introduced in f87c6fc2fa by Macintosh1011 on 2026-05-30

Summary

The /api/fetch endpoint forwards raw Node.js network error messages from the outbound HTTP request directly to the client in a 502 response (server.js:72). Different failure modes (ECONNREFUSED, EHOSTUNREACH, ETIMEDOUT, ENOTFOUND, TLS handshake errors, etc.) each yield distinguishable error strings, so attackers can differentiate closed ports, filtered ports, live hosts, and unresolved names. Combined with the server-side fetch capability, this turns the endpoint into a precise internal-network probing tool.

Impact. An unauthenticated attacker can map the server's internal network: by iterating over IPs and ports in url= parameters and observing the distinct error messages returned (e.g., 'connect ECONNREFUSED 127.0.0.1:1' versus a timeout versus a DNS failure), they can enumerate live hosts, open/closed/filtered ports, and internal service identities. This significantly accelerates lateral-movement reconnaissance against cloud metadata services, internal admin panels, and other non-public infrastructure reachable from the server.

Vulnerable code — server.js

🔴 .on("error", (e) => send(res, 502, `fetch error: ${e.message}`));

Working exploit

curl -s 'http://127.0.0.1:4600/api/fetch?url=http://127.0.0.1:1/'

Exploit transcript

$ curl -s 'http://127.0.0.1:4600/api/fetch?url=http://127.0.0.1:1/'
HTTP 502  Content-Type: text/plain
fetch error: connect ECONNREFUSED 127.0.0.1:1

Confirmed: HTTP 502 text/plain body: 'fetch error: connect ECONNREFUSED 127.0.0.1:1'

CVSS v3.1

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N5.3 (medium)

Recommended fix

AI-suggested fix — review before applying (derived from analysis of untrusted repo content):

Return a generic error to clients and log details server-side only:

--- a/server.js
+++ b/server.js
@@
-      .on("error", (e) => send(res, 502, `fetch error: ${e.message}`));
+      .on("error", (e) => {
+        console.error("upstream fetch error", { url, code: e.code, msg: e.message });
+        send(res, 502, "upstream fetch failed");
+      });

Additionally, enforce an allow-list of permitted hosts/schemes and block RFC1918, loopback, link-local, and metadata IPs before initiating the request to mitigate the underlying SSRF.

Ship the fix

Rook found it and proved it. Hand the finding — with its working exploit as a failing test — to Otis to write the fix PR.