HIGH header injectionmutual tls

Header Injection with Mutual Tls

How Header Injection Manifests in Mutual TLS

In a mutual TLS (mTLS) setup, the TLS layer guarantees that both parties present valid certificates, but it does not protect the application logic that runs after the handshake. Header injection becomes possible when data originating from the client certificate (or other client‑controlled fields) is copied into an HTTP header without proper sanitization. A common pattern is to log or forward details such as the client’s subject DN, email address, or a custom extension into headers like X-SSL-Client-Subject, X-Client-Email, or X-API-Key. If those values contain carriage‑return (\r) or line‑feed (\n) characters, an attacker can inject new headers or even split the HTTP response.

Example attack flow:

  • The attacker presents a client certificate whose SubjectAltName extension includes the string %0d%0aSet-Cookie: malicious=tracking (URL‑encoded CRLF followed by a header).
  • The server, after verifying the certificate, extracts the SAN and inserts it raw into a response header: X-Client-SAN: %0d%0aSet-Cookie: malicious=tracking.
  • When the client (or an intermediary) parses the response, it sees two headers: the intended X-Client-SAN and an injected Set-Cookie that can be used for session fixation, cookie injection, or bypassing same‑origin policies.
  • Because the TLS channel is already authenticated, the injection is not detectable at the transport layer; it appears as a legitimate header to downstream components such as caches, proxies, or application frameworks.

Other injection points include:

  • Using client‑certificate fields to construct Authorization or Custom-Auth headers for downstream services.
  • Embedding client‑provided data in Forwarded, X-Forwarded-For, or Via headers when the server acts as a reverse proxy.
  • Reflecting the client certificate’s serial number or issuer into debugging headers that are later returned in error responses.
  • These patterns are especially dangerous in mTLS because developers often assume that the certificate validation step has already “sanitized” the client, leading to a false sense of security.

Mutual TLS‑Specific Detection

Detecting header injection in an mTLS API requires checking whether any client‑derived data is reflected in HTTP response headers without validation. Manual testing can be done with tools like curl that support client certificates:

curl --cert client.pem --key client-key.pem \
     -H "Host: api.example.com" \
     https://api.example.com/endpoint \
     -v

To test for injection, replace a field in the client certificate (e.g., the commonName) with a payload containing URL‑encoded CRLF sequences (%0d%0a) followed by a test header such as X-Inject: test. If the response contains the injected header, the API is vulnerable.

middleBrick automates this check as part of its Input Validation security test (one of the 12 parallel scans). When a URL is submitted, middleBrick:

  1. Establishes a TLS connection using the supplied endpoint (no client cert is required for the scan itself).
  2. Sends a series of crafted HTTP requests where header values (e.g., User-Agent, Referer, custom headers) contain payloads like %0d%0aX-Test: injected and %0d%0aSet-Cookie: evil=1.
  3. Examines the response headers for the presence of the injected strings, taking into account that the server may reflect user‑controlled data from query strings, body, or (in an mTLS context) client‑certificate fields that are echoed back.
  4. Reports a finding with severity based on the impact (e.g., response splitting, session fixation) and provides remediation guidance.

Because middleBrick performs a black‑box scan, it does not need access to the private key or client certificate; it detects the symptom — unsafe reflection of client‑supplied data into headers — regardless of whether the data originates from the TLS layer or traditional HTTP parameters.

Mutual TLS‑Specific Remediation

The fix is to treat any data extracted from the client certificate (or any client‑controlled source) as untrusted before it is placed into an HTTP header. The remediation steps are language‑agnostic but must be applied where the certificate information is used.

1. Validate and sanitize

Define an allow‑list of safe characters (e.g., alphanumeric, hyphen, dot, @ for email) and reject or encode anything else. If you need to embed the full DN, percent‑encode it or use Base64.

2. Use safe APIs

Many HTTP libraries provide helper functions that automatically perform percent‑encoding for header values. Prefer those over manual string concatenation.

3. Code examples

LanguageUnsafe patternSafe pattern
Node.js (Express)
app.get('/api/data', (req, res) => {
  const clientDN = req.socket.getPeerCertificate()?.subject?.CN;
  // Unsafe: directly inserting client DN into a header
  res.setHeader('X-Client-CN', clientDN);
  res.json({ ok: true });
});
const { URLSearchParams } = require('url');

function safeHeaderValue(value) {
  // Allow only alphanumerics, space, hyphen, dot, @
  if (!/^[A-Za-z0-9 .\-@]+$/.test(value)) {
    throw new Error('Invalid client DN');
  }
  return value;
}

app.get('/api/data', (req, res) => {
  const cert = req.socket.getPeerCertificate();
  if (!cert?.subject?.CN) {
    return res.status(400).send('Missing client certificate');
  }
  const cn = safeHeaderValue(cert.subject.CN);
  // Safe: validated value
  res.setHeader('X-Client-CN', cn);
  res.json({ ok: true });
});
Go (net/http)
func handler(w http.ResponseWriter, r *http.Request) {
  cert := r.TLS.PeerCertificates[0]
  // Unsafe: directly using CommonName in a header
  w.Header().Set("X-Client-CN", cert.Subject.CommonName)
  json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
import (
	"crypto/x509"
	"net/http"
	"regexp"
)

var safeCN = regexp.MustCompile(`^[A-Za-z0-9 .\-@]+$`)

func handler(w http.ResponseWriter, r *http.Request) {
  if len(r.TLS.PeerCertificates) == 0 {
    http.Error(w, "client cert required", http.StatusBadRequest)
    return
  }
  cert := r.TLS.PeerCertificates[0]
  cn := cert.Subject.CommonName
  if !safeCN.MatchString(cn) {
    http.Error(w, "invalid client CN", http.StatusBadRequest)
    return
  }
  // Safe: validated CN
  w.Header().Set("X-Client-CN", cn)
  json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

4. Defense‑in‑depth

  • Deploy a Web Application Firewall (WAF) or API gateway that strips or sanitizes suspicious header values (note: middleBrick does not provide a WAF; this is an operational control).
  • Log attempts where header validation fails; sudden increases can indicate probing.
  • Ensure that error messages do not echo back raw certificate fields; use generic messages instead.

By applying these controls, you eliminate the injection vector while preserving the mutual TLS authentication guarantees.

Frequently Asked Questions

Does middleBrick need a client certificate to test for header injection in an mTLS endpoint?
No. middleBrick performs a black‑box scan that sends crafted HTTP headers and checks whether they are reflected in the response. It detects unsafe reflection of any client‑supplied data — whether from query strings, body, or certificate fields — without requiring a client certificate or private key.
If middleBrick reports a header injection finding, does it automatically fix the issue?
No. middleBrick only detects and reports the vulnerability, providing detailed remediation guidance. It does not modify, patch, or block the API; the development team must apply the recommended fixes in their code or configuration.