Man In The Middle in Adonisjs with Mutual Tls
Man In The Middle in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
In AdonisJS, enabling mutual TLS (mTLS) means the server requests and validates a client certificate during the TLS handshake. If mTLS is configured but the application does not properly verify the client certificate chain, trust anchors, or hostname bindings, the connection can still be downgraded or intercepted, creating a Man In The Middle (MitM) exposure. An attacker who can position themselves on the network might present a certificate that is trusted by the server’s CA but does not match the intended client identity, allowing them to read or modify traffic between client and server.
AdonisJS typically relies on the underlying Node.js TLS layer. A common misconfiguration is setting requestCert but failing to set rejectUnauthorized, or providing an incomplete or incorrect ca list so that the server cannot validate that the client certificate was issued by the expected CA. In such cases, an attacker with a valid but stolen or forged certificate signed by the same CA can successfully authenticate and perform a MitM, because the server never confirms that the certificate belongs to the specific client it expects. Another scenario is when the server does not check the certificate’s subject or extended key usage; a certificate provisioned for a different service could be accepted, enabling lateral movement and MitM within the API flow.
Middleware that relies on the parsed certificate without additional checks can also introduce risks. For example, extracting the common name (CN) or subject alternative names (SANs) from the client certificate and using them for routing or authorization without validating the certificate chain and revocation status can lead to privilege escalation or session hijacking. If traffic is further proxied or load-balanced without preserving end-to-end TLS and re-encrypting to the backend, an attacker who compromises an intermediate hop can terminate the TLS connection and present a certificate to the server, effectively conducting a MitM. This is particularly relevant when mTLS is enforced at the ingress but internal service-to-service calls are unencrypted or only partially protected.
Additionally, failing to rotate server-side CA certificates and not monitoring for unauthorized certificates can weaken trust over time. In an AdonisJS application that uses mTLS to authenticate clients, a compromised CA or a long-lived certificate without revocation mechanisms increases the window for MitM attacks. Runtime findings from scans often highlight missing hostname verification, absent certificate revocation checks (CRL/OCSP), and overly permissive trust stores as contributors to this class of vulnerability. Proper mutual TLS must ensure both parties authenticate each other with strong chain validation, strict hostname checks, and timely revocation to reduce MitM risk.
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
Remediation centers on strict server-side TLS configuration and robust certificate validation in AdonisJS. Ensure the server sets rejectUnauthorized to true, provides a complete and tightly scoped CA bundle, and validates hostnames. Below are concrete examples using the Node.js tls module and AdonisJS provider configuration.
Example 1: Strict mTLS server configuration in AdonisJS (start/server.ts)
import { defineConfig } from '@adonisjs/core/app'
import fs from 'fs'
export default defineConfig({
https: {
key: fs.readFileSync('path/to/server.key'),
cert: fs.readFileSync('path/to/server.crt'),
ca: fs.readFileSync('path/to/ca-bundle.crt'), // complete chain for clients
requestCert: true,
rejectUnauthorized: true, // critical: reject clients without valid cert
checkServerIdentity: (servername, cert) => {
// enforce hostname/SAN match
const allowed = ['api.example.com', 'secure.example.com']
if (!allowed.includes(servername) && !allowed.includes(cert.subject?.CN || '')) {
return new Error(`Certificate hostname mismatch: ${servername}`)
}
return undefined
},
},
})
Example 2: Middleware to enforce client certificate checks and extract verified claims
import { HttpContextContract } from '@adonisjs/core/http'
import { X509Certificate } from 'crypto'
export default class MtlsAuthMiddleware {
public async handle({ request, response, proceed }: HttpContextContract) {
const socket = request.request.socket as any
if (!socket.authorized) {
return response.unauthorized({ message: 'Client certificate required' })
}
const cert = socket.getPeerCertificate()
if (!cert || !cert.subject || !cert.issuer) {
return response.unauthorized({ message: 'Invalid client certificate' })
}
// Verify revocation (pseudo-code; integrate with CRL/OCSP in production)
const verified = await verifyCertificateRevocation(cert)
if (!verified) {
return response.unauthorized({ message: 'Certificate revoked' })
}
// Bind request identity to a verified certificate property
request.authUser = {
subject: cert.subject.CN,
issuer: cert.issuer.CN,
serialNumber: cert.serialNumber,
san: cert.subjectaltname,
}
return proceed()
}
}
async function verifyCertificateRevocation(cert: any): Promise {
// Implement CRL or OCSP checks here
// Return true if valid, false if revoked
return true
}
Example 3: Provider-side HTTPS setup with strong ciphers and TLS versions
import { defineConfig } from '@adonisjs/core/app'
import fs from 'fs'
export default defineConfig({
https: {
key: fs.readFileSync('path/to/server.key'),
cert: fs.readFileSync('path/to/server.crt'),
ca: fs.readFileSync('path/to/ca-bundle.crt'),
requestCert: true,
rejectUnauthorized: true,
ciphers: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256',
].join(':'),
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
},
})
Operational practices
- Keep the ca-bundle.crt up to date and scoped only to the issuing CAs you trust.
- Rotate server certificates and client certificates regularly; use short lifetimes where possible.
- Implement revocation checking (CRL or OCSP) and integrate it into your middleware to reject revoked certificates.
- Use the dashboard to track scan findings related to TLS and mTLS; the CLI can automate checks and output JSON for CI/CD gates.
- When running in Kubernetes or behind a proxy, ensure end-to-end mTLS is enforced and that intermediaries do not terminate TLS without re-encrypting to the backend.