Missing Authentication with Mutual Tls
How Missing Authentication Manifests in Mutual TLS
Mutual TLS (mTLS) requires both parties to present a valid certificate during the TLS handshake. When authentication is missing, one side either does not request the peer’s certificate or does not validate it correctly. This creates a classic impersonation vector: an attacker can present a self‑signed or otherwise unauthorized certificate and still complete the handshake.
- Server‑side missing client authentication – The TLS server is configured with
ClientAuth: NoClientCert(Go),requestCert: false(Node.js), orssl.CERT_NONE(Python). The server will accept any client, even one that presents no certificate or a forged one. - Client‑side missing server authentication – The client disables verification (
InsecureSkipVerify: truein Go,rejectUnauthorized: falsein Node.js,verify_mode = ssl.CERT_NONEin Python). An attacker can terminate the TLS connection with a fraudulent certificate and impersonate the legitimate service. - Weak certificate validation logic – Custom
VerifyPeerCertificatecallbacks that always returnnilor ignore the leaf certificate’s signature, allowing any certificate chain to pass.
These misconfigurations appear in code paths that build the TLS configuration before the server starts listening or before a client makes a request. For example, in a Go HTTP server the tls.Config is passed to http.Server.TLSConfig; if the ClientAuth field is not set to tls.RequireAndVerifyClientCert, the server skips client cert validation. In Node.js, the https.createServer options requestCert and rejectUnauthorized control whether the server asks for and validates a client certificate.
Attackers exploit this by:
- Connecting to the API without presenting a client certificate and still receiving a successful response.
- Presenting a certificate signed by a CA they control when the server does not verify the certificate chain against a trusted root.
- Performing a man‑in‑the‑middle attack when the client does not validate the server’s certificate, allowing the attacker to decrypt and modify traffic.
Mutual TLS‑Specific Detection
middleBrick performs unauthenticated black‑box scanning of the API endpoint. For Mutual TLS it focuses on the TLS handshake itself, checking whether the server enforces client certificate authentication and whether the client validates the server’s certificate.
The scanner proceeds as follows:
- It initiates a TLS handshake without sending a client certificate. If the server completes the handshake and returns an application‑level response (e.g., HTTP 200), middleBrick flags missing client authentication.
- It repeats the handshake with a client certificate that is signed by an untrusted CA. If the server still accepts the connection, it indicates that certificate chain validation is disabled or misconfigured.
- From the client perspective, middleBrick attempts to connect to the target using a TLS client that sets
InsecureSkipVerify‑equivalent flags. If the connection succeeds despite presenting an invalid or self‑signed server certificate, the scanner reports missing server authentication. - Additionally, middleBrick inspects any exposed configuration endpoints (e.g.,
/debug/config,/actuator/env) that may reveal TLS settings such asClientAuth: noneorverify: false.
These checks are performed in parallel with the other 11 security scans and are completed within the 5‑15 second window advertised for a full scan.
You can trigger the same detection locally with the middleBrick CLI:
# Install the CLI (npm)
npm i -g middlebrick
# Scan an API endpoint for missing mTLS authentication
middlebrick scan https://api.example.com
In a CI/CD pipeline, the GitHub Action can be used to fail a build when the mTLS‑related findings push the overall score below a threshold:
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
middlebrick:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick scan
uses: middlebrick/action@v1
with:
api-url: https://staging.example.com
fail-under: "C" # fail if score drops below C
When using an AI coding assistant, the MCP Server integration lets you scan the same endpoint directly from your IDE:
# In Cursor or Claude IDE
middlebrick scan --url https://dev.example.com
Mutual TLS‑Specific Remediation
Remediation consists of configuring the TLS stack to require and validate certificates on both sides. The exact API differs by language, but the principles are the same:
- Require the peer to present a certificate.
- Validate that certificate chain against a trusted root CA.
- Optionally, verify the certificate’s subject or SAN matches the expected identity.
Below are concrete, syntactically correct examples for the three most common backend languages.
Go (net/http)
package main import ( "crypto/tls" "crypto/x509" "io/ioutil" "log" "net/http" ) func main() { // Load the CA that issued client certificates caCert, err := ioutil.ReadFile("client_ca.pem") if err != nil { log.Fatalf("failed to read CA: %v", err) } caPool := x509.NewCertPool() caPool.AppendCertsFromPEM(caCert) // TLS config that mandates a valid client cert tlsConfig := &tls.Config{ ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: caPool, // Optional: verify the hostname matches the expected SAN VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { // verifiedChains already contains chains validated against ClientCAs if len(verifiedChains) == 0 { return fmt.Errorf("no valid client certificate chain") } // Additional checks (e.g., verify OU, SAN) can go here return nil }, } server := &http.Server{ Addr: ":8443", TLSConfig: tlsConfig, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("mTLS protected\n")) }}, } log.Fatal(server.ListenAndServeTLS("server.pem", "server.key")) }Node.js (https)
const https = require('https'); const fs = require('fs'); const options = { key: fs.readFileSync('server.key'), cert: fs.readFileSync('server.pem'), // Require clients to present a certificate requestCert: true, // Reject if the client cert is not trusted rejectUnauthorized: true, // Trust only this CA for client certs ca: fs.readFileSync('client_ca.pem'), // Optional: enforce specific SAN or OU checks checkServerIdentity: (host, cert) => { // Return undefined if hostname matches, otherwise an error return require('tls').checkServerIdentity(host, cert); } }; const server = https.createServer(options, (req, res) => { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('mTLS protected\n'); }); server.listen(8443, () => { console.log('mTLS server listening on port 8443'); });Python (ssl.SSLContext)
import ssl import socket context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) # Load server certificate and key context.load_cert_chain(certfile='server.pem', keyfile='server.key') # Require client certificates context.verify_mode = ssl.CERT_REQUIRED # Load the CA that issued client certs context.load_verify_locations(cafile='client_ca.pem') # Optional: hostname verification (default is on for CLIENT_AUTH) bindsocket = socket.socket() bindsocket.bind(('0.0.0.0', 8443)) bindsocket.listen(5) while True: newsocket, fromaddr = bindsocket.accept() connstream = context.wrap_socket(newsocket, server_side=True) try: data = connstream.recv(1024) connstream.send(b'mTLS protected\n') finally: connstream.shutdown(socket.SHUT_RDWR) connstream.close()After applying these fixes, rerun middleBrick (via CLI, GitHub Action, or MCP Server) to confirm that the missing authentication finding no longer appears and that the overall security score improves.
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 |