Distributed Denial Of Service in Adonisjs with Mutual Tls
Distributed Denial Of Service in Adonisjs with Mutual Tls
AdonisJS, a Node.js web framework, can be configured to use Mutual TLS (mTLS) where both the client and server present certificates during the TLS handshake. While mTLS strengthens authentication, this combination can introduce DDoS-relevant risks if the application layer is not hardened. In an mTLS-enabled AdonisJS server, each incoming connection requires full certificate validation, including chain verification and revocation checks (e.g., CRL or OCSP). These cryptographic operations are CPU-intensive. If an attacker sends many invalid or self-signed certificates, the server can spend disproportionate time on handshake failures, consuming event loop and CPU resources. This can reduce the number of connections the event loop can service, leading to legitimate requests experiencing high latency or timeouts, effectively creating a resource-exhaustion DDoS vector.
Another angle specific to AdonisJS is how the framework binds route handlers and middleware to the HTTP server lifecycle. If TLS termination and client certificate validation are handled in middleware, poorly optimized middleware or synchronous certificate checks can block the event loop. For example, performing heavy synchronous filesystem reads for certificate revocation checks inside a global middleware can stall incoming requests. Under sustained invalid-cert traffic, the server’s ability to accept new connections degrades. Because AdonisJS applications are often long-running processes (e.g., via Node.js cluster), a bottleneck in the request pipeline can accumulate pending connections, exhausting memory or file descriptor limits if the underlying OS or proxy (like a load balancer) does not enforce its own rate limits. This stack-specific interplay between mTLS handshake cost and AdonisJS request lifecycle can amplify the impact of unauthenticated or low-rate probing that would otherwise be benign in a non-mTLS setup.
Additionally, the OpenAPI/Swagger spec analysis performed by middleBrick demonstrates how runtime findings can highlight unexpected exposure. If an API spec includes unauthenticated endpoints alongside mTLS-required paths, but runtime probing reveals that certificate validation errors are not consistently handled, an attacker might probe to identify which endpoints fall back to weaker authentication. In such cases, the framework’s behavior around error handling for TLS failures can inadvertently reveal timing differences that facilitate adaptive DDoS techniques. For instance, if AdonisJS returns distinct status codes or response times for certificate validation failures versus successful handshakes, an attacker can use these signals to amplify resource usage by targeting the slower path. The interplay of mTLS configuration, AdonisJS error handling, and observable timing differences can therefore create a stealthy DDoS surface that is not apparent from configuration alone.
Mutual Tls-Specific Remediation in Adonisjs
To mitigate DDoS risks tied to mTLS in AdonisJS, focus on reducing CPU overhead per handshake and ensuring consistent, non-blocking error handling. Use Node.js’s built-in tls module options wisely, and avoid synchronous operations in certificate validation paths. Below are concrete code examples for an AdonisJS HTTP server using mTLS with remediation considerations.
Example 1: Basic mTLS Server Setup with Asynchronous Validation
Configure the HTTPS server to perform asynchronous certificate verification and avoid blocking the event loop. Prefer streaming checks and limit synchronous work.
const { HttpsServer } = require('@adonisjs/core/Server')
const fs = require('fs')
const tls = require('tls')
const serverOptions = {
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,
// Use a separate thread or worker for heavy CRL/OCSP checks if possible
// Node.js 16+ supports checkServerIdentity for custom validation
checkServerIdentity: (host, cert) => {
// Perform lightweight checks here; offload revocation to async sidecar
return undefined // indicates success
}
}
const server = tls.createServer(serverOptions, (secureStream) => {
// Handle connection
}).listen(443, () => {
console.log('mTLS server running on port 443')
})
// Graceful shutdown to prevent connection buildup
process.on('SIGTERM', () => {
server.close(() => {
console.log('Server closed')
})
})
Example 2: AdonisJS Middleware with Rate Limiting on TLS Errors
In AdonisJS, protect against repeated TLS handshake failures by applying rate limits on endpoints that require client certificates. This reduces the impact of clients sending many invalid certs.
import { middleware } from '@adonisjs/core'
import { RateLimiterMemory } from 'rate-limiter-flexible'
const tlsErrorLimiter = new RateLimiterMemory({
points: 10, // allow 10 TLS errors
duration: 60 // per 60 seconds
})
export const tlsErrorRateLimiter = async (ctx, next) => {
const cacheKey = `tls_error:${ctx.request.ip()}`
try {
await tlsErrorLimiter.consume(cacheKey)
await next()
} catch (rateLimitError) {
ctx.response.status(429).send({ error: 'Too Many TLS Errors' })
}
}
// Apply to routes that require mTLS
Route.post('/secure', tlsErrorRateLimiter, async (ctx) => {
// handler
})
Example 3: Offloading Certificate Revocation Checks
Avoid performing synchronous CRL file reads or blocking OCSP requests in the request path. Instead, use a background worker or external service to maintain a local cache of revoked serials and reference it asynchronously.
// pseudo-code: run a separate timer to refresh revoked serials
let revokedSerials = new Set()
async function refreshRevokedSerials() {
const data = await fetchCrlFromCa() // implement fetchCrlFromCa
revokedSerials = new Set(data.revoked)
}
setInterval(refreshRevokedSerials, 300_000) // every 5 minutes
// In middleware, check synchronously against cached set (fast)
export const checkRevoked = (certSerial) => {
if (revokedSerials.has(certSerial)) {
throw new Error('Certificate revoked')
}
}
Example 4: Connection and Payload Hardening
Limit header size and payload size to reduce memory pressure from maliciously crafted requests that exploit TLS-handshake parsing. Use timeouts and keep-alive settings appropriately.
const serverOptions = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
ca: [fs.readFileSync('ca-cert.pem')],
requestCert: true,
rejectUnauthorized: true,
maxHeadersCount: 100,
maxHeaderSize: 16384,
// Node.js server options
timeout: 30000,
keepAliveTimeout: 60000
}
By combining these practices — asynchronous validation, rate limiting on TLS errors, offloading revocation checks, and connection hardening — you reduce the DDoS surface introduced by mTLS in AdonisJS while preserving its authentication benefits.