CRITICAL command injectionmutual tls

Command Injection with Mutual Tls

How Command Injection Manifests in Mutual TLS

Mutual TLS (mTLS) adds client‑certificate verification to the standard TLS handshake. When developers need to validate a client certificate outside of the language’s TLS library, they sometimes invoke external tools such as openssl or keytool and concatenate user‑supplied data (e.g., a hostname, certificate PEM, or serial number) into a shell command. If that data is not strictly validated or escaped, an attacker can inject arbitrary shell commands.

Typical vulnerable patterns include:

  • Building an openssl verify command where the client‑certificate PEM is taken directly from a request header or JSON field and inserted into the command string.
  • Using a hostname supplied by the caller in an openssl s_client -connect call to check the server’s certificate chain, without sanitizing the hostname for shell metacharacters.
  • Parsing a client certificate’s serial number or subject with openssl x509 -noout -serial and concatenating the value into a larger script that logs or processes the result.

Because mTLS is often used in internal APIs or service‑to‑service communication, developers may assume the caller is trusted and skip input validation, opening a command‑injection vector that can lead to full host compromise.

Mutual TLS‑Specific Detection

middleBrick performs unauthenticated, black‑box scanning of the API surface. When scanning an endpoint that expects mTLS, the scanner:

  • Attempts a normal TLS handshake with a valid client certificate to establish baseline behavior.
  • Injects payloads into fields that are likely to be used in external command construction (e.g., custom headers, query parameters, JSON properties labeled “cert”, “hostname”, “cn”, “serial”).
  • Uses a set of command‑injection probes such as ; id, $(whoami), || ls -la, and `sleep 5` to detect timing or output differences.
  • Monitors for changes in response status, body, or response time that indicate the injected command was executed.

If the API reflects the output of the injected command (e.g., returns the UID from id) or shows a delayed response consistent with a sleep, middleBrick flags the finding as a command‑injection vulnerability under the "Input Validation" category, providing the exact payload that triggered the behavior and remediation guidance.

Example CLI usage:

middlebrick scan https://api.example.com/mtls-endpoint

The command returns a JSON report that includes the command‑injection finding, severity, and suggested fixes.

Mutual TLS‑Specific Remediation

The most reliable fix is to avoid invoking external commands altogether and perform all certificate validation using the language’s native TLS library. When external tooling is unavoidable, use safe subprocess APIs that accept arguments as an array and never invoke a shell.

Node.js (using tls module)

const tls = require('tls');
const fs = require('fs');

const options = {
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem'),
  ca: fs.readFileSync('ca.pem'),          // trust store for client certs
  requestCert: true,                      // require client cert
  rejectUnauthorized: true                // fail if client cert not trusted
};

const server = tls.createSecureServer(options, (socket) => {
  // socket.getPeerCertificate() provides the verified client cert
  const cert = socket.getPeerCertificate();
  // Use cert properties directly; no shell needed
  socket.write(`Hello ${cert.subject.CN}\n`);
  socket.end();
});

server.listen(8443);

Go (using crypto/tls)

package main

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"log"
	"net/http"
)

func main() {
	caCert, _ := ioutil.ReadFile("ca.pem")
	caPool := x509.NewCertPool()
	caPool.AppendCertsFromPEM(caCert)

	serverCert, _ := tls.LoadX509KeyPair("server-cert.pem", "server-key.pem")

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{serverCert},
		ClientAuth:   tls.RequireAndVerifyClientCert,
		ClientCAs:    caPool,
	}

	server := &http.Server{
		Addr:      ":8443",
		TLSConfig: tlsConfig,
		Handler:   http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.TLS == nil || len(r.TSL.PeerCertificates) == 0 {
				http.Error(w, "client cert required", http.StatusBadRequest)
				return
			}
			cert := r.TLS.PeerCertificates[0]
			w.Write([]byte("Hello " + cert.Subject.CommonName + "\n"))
		}),
	}

	log.Fatal(server.ListenAndServeTLS("", "")) // cert/key already in tlsConfig
}

Python (using ssl.SSLContext)

import ssl
import socket

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile="server-cert.pem", keyfile="server-key.pem")
context.load_verify_locations(cafile="ca.pem")
context.verify_mode = ssl.CERT_REQUIRED  # demand client cert

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.bind(('0.0.0.0', 8443))
    sock.listen(5)
    with context.wrap_socket(sock, server_side=True) as ssock:
        conn, addr = ssock.accept()
        # conn is an SSLSocket; client cert is already validated
        cert = conn.getpeercert()
        conn.sendall(f'Hello {cert.get("subject", [[('', 'CN')]])[0][1]}\n'.encode())
        conn.close()

If you must call an external tool (e.g., for legacy reasons), never build a command string with user input. Instead, pass data as separate arguments or via environment variables, and avoid shell=True.

Unsafe (example to avoid)

# DO NOT DO THIS
cmd = f'openssl verify -CAfile ca.pem {user_supplied_cert}'
os.system(cmd)

Safe alternative (Node.js)

const { execFile } = require('child_process');
execFile('openssl', ['verify', '-CAfile', 'ca.pem', certPath], (err, stdout, stderr) => {
    // handle result
});

By eliminating shell concatenation and relying on proven TLS libraries, you remove the command‑injection surface while preserving the security benefits of mutual TLS.

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 API only accepts mutual TLS connections?
Yes. middleBrick first completes a valid mTLS handshake using a trusted client certificate, then sends crafted inputs (e.g., in headers or JSON fields) that are likely to be used in external command execution. If the response changes in a way that indicates command execution, the finding is reported.
What is the safest way to validate a client certificate in an mTLS setup without risking command injection?
Use the TLS library built into your language or framework (e.g., Node.js tls, Go crypto/tls, Python ssl) to perform the handshake and certificate verification. These libraries validate the client certificate internally, eliminating the need to invoke external tools such as openssl where user data could be injected.