Information Disclosure with Mutual Tls
How Information Disclosure Manifests in Mutual TLS
Mutual TLS (mTLS) adds client‑certificate authentication to the standard TLS handshake. While this strengthens authentication, it also introduces new avenues for information disclosure if the implementation is not carefully hardened.
- Verbose handshake error messages – When a client presents an invalid or expired certificate, some TLS stacks return detailed alert messages (e.g., "certificate unknown", "certificate revoked") that can be observed in the TCP stream or in application logs. An attacker who can provoke handshake failures learns whether a particular certificate serial number is known to the server, leaking internal PKI structure.
- Logging of client certificate data – Developers often log the entire client certificate (subject, issuer, serial number) for debugging. If those logs are accessible (e.g., via debug endpoints, error pages, or log aggregation services), an attacker can harvest internal distinguished names, organizational units, or certificate policy OIDs that reveal internal hierarchy.
- Exposure of private key material via misconfigured TLS context – In environments where the server’s private key is loaded from a file with overly permissive permissions, or where the key is accidentally included in a debug dump (e.g.,
console.log(tlsContext)in Node.js), the private key can be exfiltrated. With the key, an attacker can decrypt passive traffic or impersonate the server. - Weak or anonymous cipher suites – If the server permits NULL or anonymous DH ciphers (e.g., TLS_DH_anon_WITH_AES_128_CBC_SHA), the handshake proceeds without authenticating either party. Although the session keys are still exchanged, the lack of authentication can lead to downgrade attacks where an attacker forces the use of a suite that leaks more information through side‑channels.
- Certificate transparency (CT) leakage – Some mTLS implementations embed SCTs (Signed Certificate Timestamps) in the certificate chain. If the server reflects these SCTs in error responses or debug pages, an attacker can infer which certificates have been logged publicly, potentially mapping internal services to public CT logs.
These patterns fall under OWASP API3:2019 – "Excessive Data Exposure" and also map to the "Information Disclosure" category in the OWASP API Security Top 10.
Mutual TLS‑Specific Detection
Detecting information disclosure in mTLS requires looking for the symptoms described above, both in network traffic and in application output. middleBrick’s unauthenticated black‑box scan includes checks that surface these issues:
- Data Exposure check – Scans HTTP responses (including TLS alert messages that may be reflected in error pages) for strings that resemble certificate fields (e.g., "CN=", "O=", "serialNumber"). If such strings appear in a 4xx/5xx response, the check flags a potential leak.
- Encryption check – Evaluates the TLS configuration presented during the handshake. It reports if the server allows anonymous or NULL cipher suites, if TLS versions below 1.2 are enabled, or if the server’s certificate chain contains SCTs that are reflected in clear text.
- Authentication check – Verifies whether the server actually requires a valid client certificate. If the handshake succeeds without presenting a client cert (or with a clearly invalid one) and the server still proceeds to process the request, the check notes missing enforcement, which can lead to error‑message leakage.
- LLM/AI Security check (when relevant) – For APIs that expose LLM endpoints over mTLS, the scanner also probes for prompt injection that could cause the model to echo back internal certificate data.
Example CLI usage to scan an mTLS‑protected API:
# Install the middleBrick CLI (npm)
npm i -g middlebrick
# Scan a host that expects mutual TLS
middlebrick scan https://api.internal.example.com --client-cert ./client.crt --client-key ./client.key
The command returns a JSON report where the findings array will contain entries such as:
{
"id": "DATA_EXP_004",
"name": "Certificate details exposed in error response",
"severity": "medium",
"description": "The response body contains the string 'CN=internal-ca, O=Example Corp' which may reveal internal PKI hierarchy.",
"remediation": "Strip certificate‑specific data from error messages and logs. Use generic error texts instead."
}
In a CI/CD pipeline, the GitHub Action can be configured to fail the build if any finding with severity ≥ medium is detected:
name: API Security Scan
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick scan
uses: middlebrick/github-action@v1
with:
api-url: https://staging.api.example.com
client-cert: ${{ secrets.CLIENT_CERT }}
client-key: ${{ secrets.CLIENT_KEY }}
fail-on-severity: medium
This approach gives developers immediate feedback when a configuration change unintentionally exposes certificate‑related information.
Mutual TLS‑Specific Remediation
Fixing information disclosure in mTLS focuses on eliminating unnecessary data exposure while preserving strong mutual authentication.
- Suppress certificate data in logs and error messages – Configure your TLS library to avoid logging the full client certificate. In Node.js, set the
secureContextoptions and use a customsecureConnectionlistener that only logs the result of the verification, not the certificate itself:
const tls = require('tls');
const fs = require('fs');
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt'),
ca: fs.readFileSync('ca.crt'),
requestCert: true, // require client cert
rejectUnauthorized: true, // reject if client cert invalid
// Do NOT log client cert details
};
const server = tls.createServer(options, (cleartextStream) => {
// The client certificate is available via cleartextStream.getPeerCertificate()
// but we only log the verification result
const cert = cleartextStream.getPeerCertificate();
if (cert) {
console.log(`Client cert verified: ${cert.subject.commonName}`);
} else {
console.log('No client certificate presented');
}
cleartextStream.write('HTTP/1.1 200 OK\r\n\r\nOK');
cleartextStream.end();
});
server.listen(8443);
- Disable anonymous and weak cipher suites – Explicitly define a strong cipher list. In OpenSSL‑based stacks (Node.js, Python’s ssl module) you can set the
ciphersoption to exclude NULL and anonymous DH:
# Python example using ssl.SSLContext
import ssl, socket
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile='server.crt', keyfile='server.key')
context.load_verify_locations(cafile='ca.crt')
context.verify_mode = ssl.CERT_REQUIRED
# Strong cipher suite: exclude NULL, anonymous, and weak ciphers
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA')
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(('0.0.0.0', 8443))
sock.listen(5)
while True:
conn, addr = sock.accept()
with context.wrap_socket(conn, server_side=True) as ssock:
# Handle request without leaking cert data
ssock.sendall(b'HTTP/1.1 200 OK\r\n\r\nOK')
- Ensure proper error handling – Catch TLS errors and return generic HTTP status codes (e.g., 400 Bad Request) without embedding TLS alert descriptions. In Express, you can use an error‑handling middleware that strips TLS‑specific details:
app.use((err, req, res, next) => {
if (err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' ||
err.code === 'CERT_HAS_EXPIRED' ||
err.code === 'SELF_SIGNED_CERT_IN_CHAIN') {
// Generic response – no PKI details
return res.status(400).send('Invalid client certificate');
}
next(err);
});
- Protect private key material – Ensure the key file is readable only by the process owner (
chmod 600 server.key) and never include it in container images or source control. Use secret‑management systems (e.g., HashiCorp Vault, AWS Secrets Manager) to load the key at runtime.
After applying these fixes, re‑run middleBrick (via CLI, GitHub Action, or the Dashboard) to verify that the Data Exposure and Encryption checks no longer report findings related to certificate leakage.