Bleichenbacher Attack in Hapi with Mutual Tls
Bleichenbacher Attack in Hapi with Mutual Tls
A Bleichenbacher attack exploits adaptive chosen-ciphertext in RSA-based key exchange or authentication, where an attacker submits many specially crafted ciphertexts and observes distinct error responses to gradually decrypt or recover a private key. In Hapi (a Node.js web framework), enabling Mutual TLS (mTLS) means the server requests and validates a client certificate during the TLS handshake. When mTLS is configured but application-layer authorization is not strictly enforced, an attacker can leverage the handshake success as an oracle: if the server returns different HTTP status codes or timing behavior for a missing or invalid client cert versus a valid but unauthorized cert, the attacker can iteratively modify certificate attributes or session tokens to learn about valid identities. This becomes a practical concern when Hapi services rely on mTLS for client identification but then perform additional per-request authorization checks that produce variable errors.
Consider a Hapi server that uses mutual TLS for transport-layer authentication and then maps client certificate fields to application permissions. An attacker with access to a network position where they can initiate TLS handshakes (e.g., a compromised proxy or a public endpoint) can treat the server’s responses as a Bleichenbacher oracle. For example, if the server returns 403 for a valid cert with insufficient scope and 200 for a valid and authorized cert, the attacker can craft certificate requests or session-bound tokens that probe authorization mappings. In practice, this often maps to Insecure Direct Object References (BOLA/IDOR) or broken object-level authorization, where object identifiers are exposed through timing or status-code differences. The mTLS handshake ensures a client certificate is present, but if the application does not enforce strict, consistent authorization outcomes, the handshake does not prevent an attacker from learning about valid relationships between subjects and resources.
To align with the scanner’s checks, an unauthenticated attacker can probe endpoints that rely on mTLS and observe differences in HTTP status codes, response times, or error messages. For instance, submitting requests with missing, malformed, or valid-but-unauthorized client certificates can reveal whether the server distinguishes between ‘no cert’ and ‘invalid mapping.’ This maps to findings in Authentication, BOLA/IDOR, and Property Authorization categories. MiddleBrick’s 12 checks run in parallel and include Authentication, BOLA/IDOR, and Property Authorization, which can surface such issues by comparing runtime behavior against expected consistent responses.
Mutual Tls-Specific Remediation in Hapi
Remediation focuses on ensuring that authorization decisions are independent of TLS-layer details and that error handling does not leak information. In Hapi, after the TLS handshake completes, the server should validate the client certificate and map it to an application identity once, then enforce authorization uniformly for every request. Use a centralized authorization function that returns the same error type and status code for invalid or insufficient permissions, regardless of whether the failure originates from missing cert fields, expired certs, or application-level scope mismatches.
Below are concrete Hapi examples that demonstrate a secure pattern. The first shows a basic server setup with mTLS using the built-in tls module. The second demonstrates extracting certificate fields and enforcing consistent authorization checks.
// server.js
const Hapi = require('@hapi/hapi');
const fs = require('fs');
const init = async () => {
const server = Hapi.server({
port: 443,
tls: {
key: fs.readFileSync('/path/to/server-key.pem'),
cert: fs.readFileSync('/path/to/server-cert.pem'),
ca: [fs.readFileSync('/path/to/ca-cert.pem')],
requestCert: true,
rejectUnauthorized: true
}
});
// Map certificate subject to application identity (do this once per connection)
const mapCertToSubject = (cert) => {
// Example: extract common name or a custom SAN
const subject = cert.subject.CN; // or subjectAltName
return {
id: subject,
scopes: cert.infoAccess?.map(a => a.access) || []
};
};
// Centralized authorization helper returning consistent responses
const authorize = (subject, requiredScope) => {
// Replace with your policy store lookup
const hasScope = subject.scopes.includes(requiredScope);
if (!hasScope) {
return { allowed: false, reason: 'insufficient_scope' };
}
return { allowed: true };
};
server.route({
method: 'GET',
path: '/api/resource/{id}',
options: {
handler: (request, h) => {
const cert = request.socket.getPeerCertificate();
const subject = mapCertToSubject(cert);
const auth = authorize(subject, 'read:resource');
if (!auth.allowed) {
return h.response({ error: 'insufficient_scope' }).code(403);
}
// Proceed with business logic
return { id: request.params.id, subject: subject.id };
},
tls: {
// Ensure client cert is verified at the TLS layer
requestCert: true,
rejectUnauthorized: true
}
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.error(err);
process.exit(1);
});
init();
Key points in the example:
requestCert: trueandrejectUnauthorized: trueenforce mTLS at the transport layer.mapCertToSubjectis called once per request to extract identity and scopes from the certificate, avoiding repeated parsing.authorizereturns a consistent outcome structure and status code (403) for insufficient scope, reducing oracle behavior.- All authorization decisions occur after the TLS handshake, ensuring that mTLS is used for authentication but not for fine-grained permissions alone.
For production, store policies in a dedicated service and avoid embedding scopes directly in certificates unless your PKI supports dynamic mapping. Monitor for anomalies in certificate validation failures to detect probing behavior consistent with Bleichenbacher-style attacks.