HIGH command injectionjwt tokens

Command Injection with Jwt Tokens

How Command Injection Manifests in Jwt Tokens

Command injection occurs when attacker‑controlled data is interpreted as part of an operating‑system command. In JWT‑based APIs this often happens when the raw token string is concatenated into a shell command without proper sanitisation or use of safe APIs.

Consider a Node.js endpoint that uses the jsonwebtoken library for verification but then passes the token to child_process.exec:

const jwt = require('jsonwebtoken');
const { exec } = require('child_process');

app.get('/process', (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.sendStatus(401);
  // Vulnerable: token is placed directly into a shell command
  exec(`openssl enc -base64 <<< ${token}`, (err, stdout) => {
    if (err) return res.status(500).send(err.message);
    res.send(`Decoded: ${stdout}`);
  });
});

An attacker who can create a valid JWT (e.g., by knowing the signing secret) can embed shell metacharacters such as ;, &, |, or $(…) inside the token. When the server builds the command string, those characters break out of the intended openssl call and execute arbitrary commands.

A similar pattern appears in Python with PyJWT:

import jwt
import subprocess

token = request.headers.get('Authorization', '').split()[-1]
# Vulnerable: token concatenated into a shell command
output = subprocess.check_output(f'echo {token} | base64', shell=True)
return output

In both examples the token is treated as data, but the code incorrectly places it inside a command string, enabling command injection.

Jwt Tokens-Specific Detection

middleBrick performs unauthenticated, black‑box scanning. For command injection it sends a series of payloads that contain typical shell metacharacters inside the JWT value placed in the Authorization: Bearer header. Example payloads include:

  • ; id
  • | cat /etc/passwd
  • $(whoami)
  • & sleep 5

If the endpoint reflects the output of the injected command (e.g., returns the result of id or shows a delayed response from sleep), middleBrick flags the finding as command injection with a severity rating based on the impact observed (data exfiltration, remote code execution, etc.).

The scanner also looks for indirect signs such as error messages that reveal shell syntax errors or changes in response timing, which can indicate blind command injection. Because middleBrick does not require any agents or credentials, it can test the exact same code path that a real attacker would use: a token supplied by the client.

Note that middleBrick only reports the issue; it does not attempt to exploit it beyond confirming that the payload caused a detectable change in the response.

Jwt Tokens-Specific Remediation

The fix is to never place the raw JWT (or any user‑controlled string) into a shell command. Instead, verify the token first and then use the verified claims as data. If a command must be run, avoid constructing a command string; use APIs that accept arguments separately.

Here is the corrected Node.js version:

const jwt = require('jsonwebtoken');
const { execFile } = require('child_process');

app.get('/process', (req, res) => {
  const auth = req.headers.authorization;
  if (!auth?.startsWith('Bearer ')) return res.sendStatus(401);
  const token = auth.slice(7);

  try {
    // Verify the token – rejects any tampered value
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    // Use payload data, not the raw token, for any further logic
    // Example: run a fixed command with the token passed as data via stdin
    execFile('openssl', ['enc', '-base64'], { input: token }, (err, stdout) => {
      if (err) return res.status(500).send(err.message);
      res.send(`Decoded: ${stdout}`);
    });
  } catch (err) {
    return res.status(401).send('Invalid token');
  }
});

Key changes:

  • The token is verified with jwt.verify before any use.
  • execFile is used instead of exec; the command and its arguments are supplied as an array, so the token is never interpreted by a shell.
  • The token is passed via the input option, treating it purely as data.

The equivalent safe Python example:

import jwt
import subprocess

token = request.headers.get('Authorization', '').split()[-1]
try:
    payload = jwt.decode(token, options={"verify_signature": True}, key=YOUR_SECRET)
    # Use subprocess without shell=True; pass token as stdin data
    result = subprocess.run(['openssl', 'enc', '-base64'], input=token.encode(), capture_output=True, text=True)
    return result.stdout
except jwt.InvalidTokenError:
    return 'Invalid token', 401

By verifying the token and avoiding shell concatenation, the command injection vector is eliminated while preserving the intended functionality.

Related CWEs: inputValidation

CWE IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

Can middleBrick detect command injection if the vulnerability only appears after a valid JWT is verified?
middleBrick tests the unauthenticated attack surface by injecting payloads into the JWT value placed in the Authorization header. If the endpoint first verifies the token and only then uses it in a command, the scanner will still try to inject shell metacharacters into the token. If the verification uses a secret unknown to the scanner, the injection will not succeed unless the attacker can forge a valid token. In that case middleBrick will note that the endpoint requires a correctly signed token and suggest providing a valid token for deeper testing, but it will not report a false positive.
Is it safe to use a JWT token directly in a shell command if I first verify its signature?
No. Verification guarantees the token has not been tampered with, but it does not prevent the token from containing shell metacharacters. An attacker who knows the signing secret can create a valid token that includes characters like ; or $(…). When that token is concatenated into a command string, those characters are still interpreted by the shell, leading to command injection. The proper defense is to avoid placing the token in a command context altogether or to use safe APIs that treat the token as data (e.g., execFile with argument arrays or passing the token via stdin).