HIGH jwt misconfigurationmutual tls

Jwt Misconfiguration with Mutual Tls

How Jwt Misconfiguration Manifests in Mutual Tls

Mutual TLS (mTLS) establishes a two-way authentication channel where both client and server present certificates to verify identity. When JWTs are used within this context, misconfigurations create attack vectors that exploit the trust model.

A common vulnerability occurs when JWTs are signed using the client certificate's private key, but the server fails to verify the certificate chain before processing the token. Consider this flawed implementation:

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();

app.post('/api/secure', (req, res) => {
  const cert = req.clientCert; // mTLS certificate
  const payload = { sub: cert.subject }; // extract identity from cert
  
  // Vulnerable: no certificate validation before JWT creation
  const token = jwt.sign(payload, cert.privateKey, { 
    algorithm: 'RS256' 
  });
  
  // Process token without verifying cert chain
  const decoded = jwt.verify(token, cert.publicKey);
  res.json({ message: 'Authenticated', identity: decoded.sub });
});

This pattern allows an attacker to present any certificate that can establish a TLS connection, then forge JWTs using the certificate's private key. The server trusts the certificate for TLS but doesn't validate it's from a trusted CA before using it for JWT signing.

Another critical misconfiguration involves token replay across different mTLS sessions. When JWTs are issued based on mTLS authentication but lack proper binding to the certificate's unique identifiers:

public String createJwtWithCertificateInfo(X509Certificate clientCert) {
    String certSerial = clientCert.getSerialNumber().toString();
    String certIssuer = clientCert.getIssuerDN().getName();
    
    // Vulnerable: only includes serial and issuer, not full cert hash
    Map<String, Object> claims = new HashMap<>();
    claims.put("cert_serial", certSerial);
    claims.put("cert_issuer", certIssuer);
    
    // Attacker can reuse token if they obtain the private key
    return Jwts.builder()
        .setClaims(claims)
        .signWith(privateKey, SignatureAlgorithm.RS256)
        .compact();
}

An attacker who compromises a client certificate's private key can create valid JWTs that will be accepted by any service expecting that certificate, even if the actual certificate isn't presented during the TLS handshake.

Time-based token expiration without certificate revocation checking creates another vulnerability. Consider:

func handleRequest(w http.ResponseWriter, r *http.Request) {
    cert, err := getClientCert(r.TLS);
    if err != nil { http.Error(w, "No cert", 401); return }
    
    token := extractJWTFromHeader(r);
    claims, err := jwt.ParseWithCert(token, cert);
    
    // Vulnerable: no check if certificate is revoked
    if err == nil {
        w.Write([]byte("Access granted"));
    }
}

If the client certificate is revoked due to compromise but the JWT hasn't expired, the server will still accept the token until expiration, creating a window of vulnerability.

Mutual Tls-Specific Detection

Detecting JWT misconfigurations in mTLS environments requires examining both the certificate handling and token processing logic. The key indicators include:

Certificate Validation Gaps - Tools should verify that servers validate the complete certificate chain before accepting JWTs signed with client certificates. This includes checking:

  • Certificate is issued by a trusted CA in the server's trust store
  • Certificate hasn't been revoked (via OCSP or CRL)
  • Certificate is within its validity period
  • Certificate matches expected client identity

Token Binding Verification - JWTs in mTLS contexts should be cryptographically bound to the certificate used for authentication. Detection tools should check for:

# Check for certificate thumbprint binding in JWT claims
# A properly bound token includes a certificate fingerprint
THUMBPRINT=$(openssl x509 -in client.crt -noout -fingerprint)

# The JWT should contain this exact thumbprint in a custom claim
# Missing or mismatched thumbprint indicates potential misconfiguration

Mutual Authentication Bypass - Scan for endpoints that accept JWTs without requiring the client certificate for token processing:

# Test if endpoint accepts token without client cert
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/secure

# Should require client cert, but misconfigured endpoints may not

Certificate Reuse Detection - Tools should identify if JWTs can be reused across different mTLS sessions by:

  1. Capturing a valid JWT from one mTLS session
  2. Attempting to use it with a different client certificate
  3. Checking if the server accepts the token without certificate validation

middleBrick's mTLS-Specific Scanning - middleBrick's black-box scanner tests mTLS endpoints by:

  • Attempting connections with both valid and expired client certificates
  • Checking if JWTs are accepted without proper certificate validation
  • Testing token replay across different certificate contexts
  • Verifying certificate revocation checking is implemented

The scanner provides a security score and detailed findings including specific certificate validation failures and JWT binding issues.

Mutual Tls-Specific Remediation

Remediating JWT misconfigurations in mTLS environments requires implementing proper certificate validation and token binding. Here are specific fixes for common vulnerabilities:

Certificate Chain Validation - Always validate the complete certificate chain before processing JWTs:

public boolean validateClientCertificate(X509Certificate cert) {
    try {
        // Validate against trusted CAs
        cert.verify(trustedCAPublicKey);
        
        // Check revocation status
        if (isCertificateRevoked(cert)) {
            return false;
        }
        
        // Verify certificate is within validity period
        if (!cert.isValid()) {
            return false;
        }
        
        return true;
    } catch (Exception e) {
        return false;
    }
}

// Use in JWT processing
X509Certificate clientCert = getClientCertFromTLS();
if (!validateClientCertificate(clientCert)) {
    throw new SecurityException("Invalid client certificate");
}

Certificate Binding in JWT Claims - Include certificate-specific information in JWT claims to prevent token reuse:

import hashlib
import jwt
from datetime import datetime, timedelta

def create_bound_jwt(client_cert, private_key):
    # Extract certificate fingerprint
    cert_der = client_cert.dump()
    cert_fingerprint = hashlib.sha256(cert_der).hexdigest()
    
    # Include certificate binding in claims
    claims = {
        'sub': client_cert.get_subject().CN,
        'cert_fingerprint': cert_fingerprint,
        'iat': datetime.utcnow(),
        'exp': datetime.utcnow() + timedelta(hours=1)
    }
    
    # Sign with server private key (not client cert)
    token = jwt.encode(claims, private_key, algorithm='RS256')
    return token

Certificate Revocation Checking - Implement real-time revocation checking:

public bool IsCertificateRevoked(X509Certificate2 cert)
{
    X509Chain chain = new X509Chain();
    chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
    
    if (!chain.Build(cert))
    {
        foreach (X509ChainElement element in chain.ChainElements)
        {
            if (element.ChainElementStatus.Any(status => 
                status.Status == X509ChainStatusFlags.Revoked))
            {
                return true;
            }
        }
    }
    return false;
}

Secure Token Issuance - Use server-side signing rather than client certificate private keys:

func IssueSecureToken(clientCert *x509.Certificate, serverKey *rsa.PrivateKey) (string, error) {
    // Validate certificate first
    if !validateCertificate(clientCert) {
        return "", errors.New("invalid certificate")
    }
    
    // Create claims with certificate binding
    claims := jwt.MapClaims{
        "sub": clientCert.Subject.CommonName,
        "cert_serial": clientCert.SerialNumber.String(),
        "cert_fingerprint": fingerprintCertificate(clientCert),
        "iat": time.Now().Unix(),
        "exp": time.Now().Add(1 * time.Hour).Unix(),
    }
    
    // Sign with server key, not client cert
    token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
    return token.SignedString(serverKey)
}

middleBrick Integration - After implementing these fixes, use middleBrick's CLI to verify remediation:

# Scan your mTLS endpoint
middlebrick scan https://api.example.com/secure \
  --client-cert client.crt \
  --client-key client.key

# Check the report for certificate validation findings
# The tool will verify:
# - Certificate chain validation
# - Token binding to certificate
# - Revocation checking implementation
# - Proper mTLS authentication requirements

Related CWEs: authentication

CWE IDNameSeverity
CWE-287Improper Authentication CRITICAL
CWE-306Missing Authentication for Critical Function CRITICAL
CWE-307Brute Force HIGH
CWE-308Single-Factor Authentication MEDIUM
CWE-309Use of Password System for Primary Authentication MEDIUM
CWE-347Improper Verification of Cryptographic Signature HIGH
CWE-384Session Fixation HIGH
CWE-521Weak Password Requirements MEDIUM
CWE-613Insufficient Session Expiration MEDIUM
CWE-640Weak Password Recovery HIGH

Frequently Asked Questions

Can JWTs be securely used with mTLS, or should I avoid them entirely?
JWTs can be securely used with mTLS when properly implemented. The key is ensuring JWTs are bound to the client certificate through claims like certificate fingerprints, and that the server validates the complete certificate chain before processing tokens. Use server-side signing rather than client certificate private keys, and implement certificate revocation checking. middleBrick can help verify your implementation by testing for these security controls.
How does certificate revocation work with JWTs in mTLS environments?
Certificate revocation checking should occur before JWT processing, not after. When a client presents a certificate, verify it hasn't been revoked using OCSP or CRL checks before accepting any JWTs. Additionally, include certificate serial numbers or fingerprints in JWT claims so you can detect if a revoked certificate's token is being reused. middleBrick's scanner tests for proper revocation checking by attempting connections with revoked certificates and verifying the server's response.