Token Leakage with Mutual Tls
How Token Leakage Manifests in Mutual Tls
Token leakage in Mutual Tls environments presents unique challenges because the authentication mechanism itself becomes part of the attack surface. In Mutual Tls (mTLS), both client and server authenticate using certificates, but tokens—whether JWTs, API keys, or session identifiers—still flow through the established secure channel. The primary attack vectors emerge from improper certificate handling and token transmission patterns.
The most common manifestation occurs when mTLS certificates are cached or stored in memory alongside tokens. Consider a Node.js Express server using mTLS for authentication:
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
const options = {
cert: fs.readFileSync('server-cert.pem'),
key: fs.readFileSync('server-key.pem'),
ca: [fs.readFileSync('client-cert.pem')],
requestCert: true,
rejectUnauthorized: true
};
https.createServer(options, app).listen(443, () => {
console.log('Server running on port 443');
});
app.get('/api/data', (req, res) => {
const clientCert = req.connection.getPeerCertificate();
const token = req.headers.authorization; // Token leakage risk
// Certificate and token both accessible in same context
if (clientCert && token) {
// Process request
}
});The critical vulnerability here is that the certificate verification (mTLS) and token validation occur in the same request lifecycle. If an attacker compromises the certificate store or gains access to memory dumps, both the mTLS certificate and any tokens become exposed simultaneously.
Another manifestation appears in certificate rotation scenarios. When mTLS certificates expire and rotate, applications often maintain both old and new certificates in memory during transition periods. If token refresh mechanisms aren't properly isolated, leaked tokens from the old certificate context can be reused with the new certificate, creating a window of vulnerability.
Database connection strings represent another significant risk. Many mTLS implementations store database credentials alongside certificate paths:
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD, // Token leakage risk
ssl: {
cert: fs.readFileSync('/etc/ssl/client-cert.pem'),
key: fs.readFileSync('/etc/ssl/client-key.pem'),
ca: fs.readFileSync('/etc/ssl/ca-cert.pem')
}
};
const pool = new Pool(dbConfig);Here, database credentials (effectively tokens) are stored in plaintext configuration alongside certificate paths. If the configuration file is improperly secured or logged, both authentication mechanisms become compromised.
Log injection attacks specifically target mTLS environments. When applications log mTLS handshake details for debugging, they often inadvertently log token values that were transmitted during the same request:
app.use((req, res, next) => {
const cert = req.connection.getPeerCertificate();
console.log(`mTLS: CN=${cert.subject.CN}, token=${req.headers.authorization}`);
next();
});This pattern creates a perfect storm where certificate information and token data are logged together, making log files a treasure trove for attackers who gain access.
Mutual Tls-Specific Detection
Detecting token leakage in mTLS environments requires specialized scanning that understands the certificate lifecycle and token transmission patterns. Traditional security scanners miss mTLS-specific vulnerabilities because they don't analyze the certificate-to-token relationship.
middleBrick's mTLS detection methodology examines several critical areas. First, it analyzes certificate storage patterns by examining file system permissions and memory access patterns. The scanner identifies when certificates are stored in world-readable directories or when certificate data is exposed through debugging endpoints.
The scanner specifically looks for these mTLS token leakage patterns:
middlebrick scan https://api.example.com --mTLS --verbose
# Output example:
[CRITICAL] Certificate path exposure detected
- /etc/ssl/certs/client-cert.pem is world-readable
- Certificate data accessible via /debug/cert endpoint
[HIGH] Token storage vulnerability
- Database credentials stored in plaintext configuration
- Tokens cached alongside certificate data in memory
[MEDIUM] Log injection risk
- mTLS handshake details logged with token values
- Certificate serial numbers exposed in debug logsmiddleBrick's mTLS scanner performs active probing by establishing mTLS connections to test endpoints and analyzing the certificate exchange process. It identifies when applications fail to properly isolate certificate verification from token validation, a common pattern that leads to token leakage.
The scanner examines configuration files for insecure patterns:
# Vulnerable configuration - detected by middleBrick
database:
host: db.example.com
username: ${DB_USER}
password: ${DB_PASSWORD} # Plaintext token exposure
ssl:
cert: /etc/ssl/client-cert.pem # World-readable path
key: /etc/ssl/client-key.pem
ca: /etc/ssl/ca-cert.pemmiddleBrick also analyzes runtime behavior by monitoring certificate lifecycle events. When certificates rotate, the scanner checks whether token refresh mechanisms maintain proper isolation. It detects if old tokens remain valid during certificate transition periods.
The tool specifically tests for log injection vulnerabilities by sending requests that trigger debug logging and analyzing the logged output for token exposure. It also examines whether applications properly sanitize certificate information before logging.
For containerized environments, middleBrick scans Docker configurations to identify when mTLS certificates are mounted with excessive permissions or when environment variables containing tokens are exposed through container inspection mechanisms.
Mutual Tls-Specific Remediation
Remediating token leakage in mTLS environments requires architectural changes that properly isolate certificate authentication from token-based authorization. The goal is to ensure that compromise of one mechanism doesn't automatically compromise the other.
The first remediation step is implementing certificate-based token binding. Instead of treating mTLS and tokens as separate authentication layers, bind tokens to specific certificates:
const crypto = require('crypto');
function bindTokenToCertificate(token, certificate) {
const certFingerprint = crypto.createHash('sha256')
.update(certificate.raw)
.digest('hex');
return `${token}--${certFingerprint}`;
}
function verifyTokenBinding(token, certificate) {
const parts = token.split('--');
if (parts.length !== 2) return false;
const [rawToken, fingerprint] = parts;
const expectedFingerprint = crypto.createHash('sha256')
.update(certificate.raw)
.digest('hex');
return rawToken && fingerprint === expectedFingerprint;
}This approach ensures that even if a token is leaked, it cannot be used without the corresponding certificate, and vice versa.
For certificate storage, implement secure key management with proper access controls:
# Secure certificate storage
mkdir -p /opt/mtls/certs
chmod 700 /opt/mtls/certs
chown root:root /opt/mtls/certs
# Only allow specific processes to access certificates
setfacl -m u:www-data:rx /opt/mtls/certs
# Use environment variables for sensitive paths
export MTLS_CERT_PATH=/opt/mtls/certs/client-cert.pem
export MTLS_KEY_PATH=/opt/mtls/certs/client-key.pemImplement proper logging sanitization to prevent token leakage through logs:
function sanitizeLog(message, sensitiveData) {
const sanitized = message.replace(
/(?:
(?:token|authorization|bearer)\s*[:=]\s*"([^"]+)"| # Quoted tokens
(?:token|authorization|bearer)\s*[:=]\s*'([^']+)')| # Single-quoted tokens
(?:token|authorization|bearer)\s*[:=]\s*([^\s,]+)| # Unquoted tokens
-----BEGIN\s+CERTIFICATE-----[^-]*-----END\s+CERTIFICATE----- # Certificates
)/gim,
'[REDACTED]'
);
return sanitized;
}
app.use((req, res, next) => {
const cert = req.connection.getPeerCertificate();
const sanitizedCert = sanitizeLog(JSON.stringify(cert), null);
console.log(`mTLS: ${sanitizedCert}`);
next();
});For database connections, use mTLS-native authentication instead of token-based credentials where possible:
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
ssl: {
cert: fs.readFileSync('/opt/mtls/certs/db-client-cert.pem'),
key: fs.readFileSync('/opt/mtls/certs/db-client-key.pem'),
ca: fs.readFileSync('/opt/mtls/certs/ca-cert.pem'),
rejectUnauthorized: true
},
// No password needed - mTLS handles authentication
});Implement certificate lifecycle management with proper token rotation:
const axios = require('axios');
async function refreshCredentials() {
try {
// Get new certificate
const certResponse = await axios.get('/api/cert/rotate', {
httpsAgent: new https.Agent({ rejectUnauthorized: false })
});
// Get new token bound to new certificate
const tokenResponse = await axios.post('/api/auth/token', {
certificateFingerprint: crypto.createHash('sha256')
.update(certResponse.data.raw)
.digest('hex')
});
return {
certificate: certResponse.data,
token: tokenResponse.data.token
};
} catch (error) {
console.error('Credential refresh failed:', error);
throw error;
}
}Finally, implement runtime monitoring to detect token leakage attempts:
const tokenLeakageDetector = require('token-leakage-detector');
// Monitor for suspicious patterns
tokenLeakageDetector.on('leakage-detected', (event) => {
console.warn('Token leakage detected:', event);
// Alert security team
alertSecurityTeam(event);
});
// Scan memory for exposed tokens
setInterval(() => {
tokenLeakageDetector.scanMemoryForTokens();
}, 60000);