Time Of Check Time Of Use with Mutual Tls
How Time Of Check Time Of Use Manifests in Mutual TLS
Mutual TLS (mTLS) authenticates both parties by validating X.509 certificates during the TLS handshake. A Time Of Check Time Of Use (TOCTOU) flaw appears when the application validates the client certificate only once — typically at connection establishment — and then reuses the authenticated TLS session for multiple requests without re‑validating the certificate for each request. If an attacker can obtain or hijack a valid session after the initial check, they can send malicious requests that are trusted because the server still believes the session is bound to the original client certificate.
Common code paths where this occurs include connection pooling, HTTP keep‑alive, and TLS session resumption. For example, a Go HTTP server that sets tls.Config.ClientAuth = tls.RequireAndVerifyClientCert but leaves session tickets enabled will allow a client to complete a successful handshake, receive a session ticket, and later present that ticket to resume a session. The server will skip certificate verification on resumption, accepting any data encrypted with the resumed keys.
Attackers can exploit this by:
- Stealing a valid session ticket from a legitimate client (e.g., via side‑channel or memory dump).
- Forcing a TLS renegotiation that reuses the original client certificate but allows the attacker to inject unauthenticated data after the check.
- Abusing misconfigured load balancers that terminate mTLS and forward plain HTTP to backend services, trusting the front‑end’s certificate check.
Mutual TLS-Specific Detection
middleBrick’s black‑box scan includes an Encryption check that probes for Mutual TLS usage and validates whether the server enforces per‑request client certificate verification. The scanner attempts a full TLS handshake with a valid client certificate, captures the session ticket (if offered), and then replays that ticket in a new connection without presenting a certificate. If the server accepts the request, middleBrick flags a potential TOCTOU condition in the Mutual TLS implementation.
Because the scan is unauthenticated and agent‑less, you only need to provide the public API URL. Example using the middleBrick CLI:
middlebrick scan https://api.example.com/health
The output will list the finding under the Encryption category, with severity, a short description, and remediation guidance. If the server does not offer session tickets or forces re‑verification on each connection, the check will pass and no finding will be reported.
Mutual TLS-Specific Remediation
The most reliable fix is to bind the client certificate validation to every request, eliminating the window where a stale session could be abused. This can be done by disabling TLS session resumption or by configuring the TLS library to verify the certificate on each resumption attempt.
Go (net/http)
tlsConfig := &tls.Config{ ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: caPool, // Disable session tickets to prevent resumption without re‑check SessionTicketsDisabled: true, // Optional: enforce verification on each resumption VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { // Perform any additional checks (e.g., OCSP, CRL) here return nil }, } httpServer := &http.Server{ Addr: "0.0.0.0:8443", Handler: mux, TLSConfig: tlsConfig, } httpServer.ListenAndServeTLS("cert.pem", "key.pem")Node.js (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'), requestCert: true, rejectUnauthorized: true, // Disable session tickets and session reuse sessionIdContext: Buffer.from('nodejs-mtls'), honorCipherOrder: true, // Force verification on each handshake secureProtocol: 'TLSv1_2_method', sessionTimeout: 0, }; const server = tls.createServer(options, (socket) => { // socket.authorized is true only if the client cert validated if (!socket.authorized) { socket.write('Handshake failed\n'); socket.end(); return; } // Process request knowing the cert is fresh socket.write('Hello authenticated client\n'); socket.end(); }); server.listen(8443, () => { console.log('mTLS server listening on port 8443'); });Java (SSLEngine)
SSLContext ctx = SSLContext.getInstance("TLSv1.2"); ctx.init(keyManagers, trustManagers, null); SSLEngine engine = ctx.createSSLEngine(); engine.setNeedClientAuth(true); // Disable session caching to force full handshake each time engine.setEnableSessionCreation(false); // Optional: enable OCSP checking via PKIX parameters // ...After applying these changes, re‑run middleBrick to confirm the finding is resolved. The scanner will no longer accept a resumed session without a fresh client certificate, confirming that the TOCTOU window has been closed.