Broken Authentication with Mutual Tls
How Broken Authentication Manifests in Mutual Tls
Mutual TLS (mTLS) authentication failures often stem from improper certificate validation, misconfigured trust chains, or incomplete certificate lifecycle management. In mTLS implementations, broken authentication typically manifests through several specific attack vectors.
One common pattern involves certificate pinning bypass. When applications hardcode certificate fingerprints or public keys without proper validation, attackers can exploit certificate rotation or introduce rogue certificates that still match the pinned value. For example, a Node.js server might validate certificates using:
const tls = require('tls');
const fs = require('fs');
const options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
requestCert: true,
rejectUnauthorized: true,
ca: [fs.readFileSync('ca-cert.pem')],
checkServerIdentity: (host, cert) => {
// Broken: only checking CN, not SAN
if (cert.subject.CN !== host) {
throw new Error('Certificate subject mismatch');
}
}
};
const server = tls.createServer(options, (socket) => {
// Authentication complete
});
This implementation is vulnerable because it only checks the Common Name (CN) field while ignoring Subject Alternative Names (SAN), allowing certificate spoofing for alternate hostnames.
Another mTLS-specific authentication break occurs through improper certificate revocation checking. Many implementations skip CRL (Certificate Revocation List) or OCSP (Online Certificate Status Protocol) validation for performance reasons, creating windows where revoked certificates remain valid. The vulnerability manifests when:
const https = require('https');
const agent = new https.Agent({
rejectUnauthorized: true,
// Missing: checkServerIdentity for mutual auth
// Missing: certificate revocation checking
});
https.request({
hostname: 'api.example.com',
port: 443,
path: '/resource',
method: 'GET',
agent: agent
}, (res) => {
// Process response
});
Without explicit revocation checks, compromised certificates remain usable until expiration.
Certificate chain validation bypasses represent another mTLS-specific authentication break. Applications may accept certificates with weak intermediate CA signatures or missing intermediate certificates in the chain. This allows attackers to present self-signed certificates that superficially appear valid:
function validateCertificateChain(cert) {
// Broken: not validating intermediate CA
if (cert.issuer === 'CN=MyCompany CA') {
return true;
}
return false;
}
// Attacker can forge: Issuer: CN=MyCompany CA, but self-signed
Time-based certificate validation failures also create authentication breaks. Applications that don't properly handle clock skew or use weak time comparisons may accept expired or not-yet-valid certificates:
function isCertificateValid(cert) {
const now = Date.now();
// Vulnerable to timing attacks and clock skew
return now >= cert.notBefore && now <= cert.notAfter;
}
// Attacker with clock manipulation or during certificate rollover windows
Mutual Tls-Specific Detection
Detecting broken authentication in mTLS requires testing certificate validation logic, trust chain integrity, and authentication completeness. Automated scanning tools like middleBrick can identify these specific mTLS vulnerabilities through black-box testing.
middleBrick's mTLS detection capabilities include:
| Detection Type | Test Method | Indicator |
|---|---|---|
| Certificate Validation Bypass | Present expired/invalid certificates | Server accepts without rejection |
| Trust Chain Verification | Supply self-signed intermediate certificates | Missing intermediate CA rejection |
| Revocation Checking | Present revoked certificates | No revocation status validation |
| Certificate Pinning Bypass | Rotate certificates with same fingerprint | Pinning doesn't validate full chain |
Manual detection techniques include using OpenSSL to test certificate validation:
# Test with expired certificate
openssl s_client -connect api.example.com:443 \
-cert expired-cert.pem \
-key expired-key.pem \
-CAfile ca-cert.pem
# Test with self-signed intermediate
openssl s_client -connect api.example.com:443 \
-cert client-cert.pem \
-key client-key.pem \
-CAfile self-signed-ca.pem
# Test revocation status
openssl s_client -connect api.example.com:443 \
-cert revoked-cert.pem \
-key revoked-key.pem \
-CAfile ca-cert.pem
Network-level detection using Wireshark or tcpdump can reveal authentication bypasses:
# Capture mTLS handshake and certificate exchange
tcpdump -i eth0 -w mtls-capture.pcap port 443
# Analyze certificate details
openssl x509 -in captured-cert.pem -text -noout
# Check for missing revocation information
openssl verify -verbose -CAfile ca-cert.pem client-cert.pem
middleBrick specifically tests mTLS endpoints by attempting connections with:
- Expired certificates
- Certificates with invalid signatures
- Missing intermediate CA certificates
- Revoked certificates (if CRL/OCSP endpoints are accessible)
- Certificates with mismatched hostnames
The scanner reports authentication failures with severity levels based on the security impact and likelihood of exploitation.
Mutual Tls-Specific Remediation
Effective mTLS authentication remediation requires implementing comprehensive certificate validation, proper trust chain verification, and robust certificate lifecycle management. Here are specific code-level fixes for mTLS authentication breaks.
Complete certificate chain validation using Node.js:
const tls = require('tls');
const fs = require('fs');
function createSecureMTLSContext() {
const caCerts = fs.readFileSync('ca-bundle.pem');
return {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
requestCert: true,
rejectUnauthorized: true,
ca: [caCerts],
checkServerIdentity: (host, cert) => {
// Complete validation: check SAN and CN
const subjectAltNames = cert.subjectaltname || '';
const validHosts = subjectAltNames
.split(',')
.map(s => s.trim())
.filter(s => s.startsWith('DNS:'))
.map(s => s.substring(4));
validHosts.push(cert.subject.CN);
if (!validHosts.includes(host)) {
throw new Error(`Certificate not valid for ${host}`);
}
},
secureOptions: require('constants').SSL_OP_NO_TLSv1 | require('constants').SSL_OP_NO_TLSv1_1,
honorCipherOrder: true,
minVersion: 'TLSv1.2'
};
}
const server = tls.createServer(createSecureMTLSContext(), (socket) => {
// Authentication complete - certificate is valid
const cert = socket.getPeerCertificate();
console.log('Authenticated client:', cert.subject.CN);
});
Certificate revocation checking implementation:
const { X509Certificate } = require('crypto');
const axios = require('axios');
async function checkCertificateRevocation(certPem) {
const cert = X509Certificate.fromPEM(certPem);
// Check certificate validity period
const now = Date.now();
if (now < cert.validFrom || now > cert.validTo) {
throw new Error('Certificate expired or not yet valid');
}
// Check for revocation information in certificate
const ext = cert.extensions.find(e => e.name === 'crlDistributionPoints');
if (ext) {
const crlUrls = ext.critical ? ext.value : ext.value.split(',').map(s => s.trim());
for (const url of crlUrls) {
try {
const crlResponse = await axios.get(url, { timeout: 5000 });
const crl = parseCRL(crlResponse.data); // Implement CRL parsing
if (crl.isRevoked(cert.serialNumber)) {
throw new Error('Certificate revoked');
}
} catch (e) {
console.warn('CRL check failed:', e.message);
}
}
}
return true;
}
Certificate pinning with full chain validation:
const crypto = require('crypto');
class MTLSVerifier {
constructor(trustedCAs, pinnedPublicKeys) {
this.trustedCAs = trustedCAs;
this.pinnedPublicKeys = pinnedPublicKeys;
}
verifyCertificateChain(certChain) {
// Validate trust chain
const trusted = this.validateTrustChain(certChain);
if (!trusted) {
throw new Error('Untrusted certificate chain');
}
// Validate pinning
const leafCert = certChain[0];
const leafPublicKey = leafCert.publicKey.toString('base64');
if (!this.pinnedPublicKeys.includes(leafPublicKey)) {
throw new Error('Certificate pinning mismatch');
}
return true;
}
validateTrustChain(chain) {
// Implement proper chain validation
// Check signatures, expiration, revocation
return true;
}
}
Time-based validation with clock skew tolerance:
function isCertificateValidWithSkew(cert, clockSkewMinutes = 5) {
const now = Date.now();
const skewMs = clockSkewMinutes * 60 * 1000;
return now + skewMs >= cert.notBefore &&
now - skewMs <= cert.notAfter;
}
// Usage in TLS context
const options = {
...,
checkServerIdentity: (host, cert) => {
if (!isCertificateValidWithSkew(cert)) {
throw new Error('Certificate timing invalid');
}
// Additional validation...
}
};
These remediation techniques address the most common mTLS authentication breaks by ensuring complete certificate validation, proper trust chain verification, and robust lifecycle management.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |