Dns Cache Poisoning in Adonisjs with Mutual Tls
Dns Cache Poisoning in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
DNS cache poisoning (also known as DNS spoofing) occurs when an attacker injects a malicious DNS response that causes a resolver to cache an incorrect mapping between a hostname and an IP address. In AdonisJS applications that rely on external hostnames—such as OAuth providers, payment gateways, or internal microservices—this can redirect traffic to a malicious endpoint. When mutual TLS (mTLS) is enabled, the application presents a client certificate during the TLS handshake, which some developers assume fully authenticates the server. However, mTLS protects the identity of the client and ensures server authentication only if the server’s hostname matches the certificate and DNS resolution is trustworthy.
With AdonisJS, DNS queries are typically performed at runtime by underlying Node.js modules such as dns.lookup or by HTTP clients like axios and got. If DNS resolution is performed before or during the TLS handshake without strict hostname verification, an attacker on the network path may poison the resolver’s cache. Because mTLS requires the client to validate the server certificate, a poisoned DNS entry that points to a server controlled by the attacker will still present a certificate. If the AdonisJS client does not properly validate the hostname against the certificate’s Subject Alternative Name (SAN) or Common Name (CN), it may accept the connection, leading to a man-in-the-middle scenario despite mTLS being in place.
The specific combination of AdonisJS and mTLS can expose this vulnerability when developers configure HTTP clients to skip hostname verification for convenience or due to misconfigured certificate environments. For example, if an AdonisJS service uses a custom HTTP client with rejectUnauthorized set to false, or does not supply a CA bundle that constrains trust to specific issuers, the client may trust any certificate presented by the IP address returned via poisoned DNS. Additionally, if the application uses environment variables to dynamically set hostnames for API endpoints, and those values are not validated or overridden with strict hostname checks, the risk increases. The mTLS handshake will succeed because the client certificate is presented and accepted, but the server identity remains unverified, allowing an attacker to intercept or manipulate traffic.
To understand the impact, consider an AdonisJS application that calls a payment API using a hostname stored in configuration. If the DNS record for that hostname is poisoned to point to an attacker’s server, and the AdonisJS HTTP client does not enforce strict hostname validation, the mTLS-protected request may be sent to the attacker. Because mTLS ensures the client presents a valid certificate, the attacker’s server may also present a valid certificate for that hostname—if they can obtain one through compromise or misissuance—or simply ignore hostname validation. The application may then unknowingly send sensitive data to the malicious endpoint, believing the communication is protected by mTLS.
Defending against DNS cache poisoning in this context requires ensuring that DNS resolution and certificate validation are tightly coupled. AdonisJS applications should enforce hostname verification in all HTTP clients, use pinned certificates or public key pinning where feasible, and avoid relying solely on mTLS without validating the server hostname. Network-level protections such as DNSSEC are outside the scope of application code but should be encouraged as part of a defense-in-depth strategy.
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on ensuring that hostname validation is always performed, even when mTLS is used. In AdonisJS, HTTP clients are often configured via services or custom wrappers. The following examples demonstrate how to enforce strict certificate and hostname validation using the built-in https module and axios, a commonly used HTTP client in AdonisJS projects.
1. Using Node.js https module with mTLS and strict hostname verification
Always provide a CA certificate, set checkServerIdentity to validate the hostname against the certificate, and avoid disabling certificate validation.
const https = require('https');
const fs = require('fs');
const agent = new https.Agent({
cert: fs.readFileSync('/path/to/client-cert.pem'),
key: fs.readFileSync('/path/to/client-key.pem'),
ca: fs.readFileSync('/path/to/ca-bundle.pem'),
rejectUnauthorized: true,
checkServerIdentity: (host, cert) => {
const tls = require('tls');
const verifyOptions = {
host: host,
cert: cert
};
const verified = tls.checkServerIdentity(verifyOptions);
if (verified instanceof Error) {
return verified;
}
return undefined; // No error means validation passed
}
});
// Example request within an AdonisJS command or service
const request = https.get('https://api.example.com/endpoint', { agent }, (res) => {
console.log('statusCode:', res.statusCode);
res.on('data', (d) => process.stdout.write(d));
});
request.on('error', (e) => {
console.error(e);
});
2. Using axios with mTLS and strict hostname validation
Configure axios to use an HTTPS agent with proper client certificates and ensure that the server hostname is validated.
const https = require('https');
const fs = require('fs');
const axios = require('axios');
const agent = new https.Agent({
cert: fs.readFileSync('/path/to/client-cert.pem'),
key: fs.readFileSync('/path/to/client-key.pem'),
ca: fs.readFileSync('/path/to/ca-bundle.pem'),
rejectUnauthorized: true,
checkServerIdentity: (host) => {
// tls.checkServerIdentity is used internally by Node.js when rejectUnauthorized is true,
// but we can add extra validation logic here if needed.
return undefined;
}
});
async function callPaymentApi() {
try {
const response = await axios.get('https://api.example.com/charge', {
httpsAgent: agent,
validateStatus: (status) => status >= 200 && status < 300
});
console.log(response.data);
} catch (error) {
console.error('Request failed:', error.message);
}
}
callPaymentApi();
3. Centralizing HTTP client creation in AdonisJS providers
In an AdonisJS application, create a singleton HTTP client in a provider or a factory to ensure consistent settings across the app. Avoid overriding rejectUnauthorized or disabling checkServerIdentity.
// start/hooks.ts or a dedicated service file
import { Http } from '@adonisjs/core';
import https from 'https';
import fs from 'fs';
const tlsAgent = new https.Agent({
cert: fs.readFileSync('/path/to/client-cert.pem'),
key: fs.readFileSync('/path/to/client-key.pem'),
ca: fs.readFileSync('/path/to/ca-bundle.pem'),
rejectUnauthorized: true,
});
export const secureHttpClient = Http.client.use({ httpsAgent: tlsAgent });
// Usage in a controller
import { secureHttpClient } from '#start/hooks';
export default class PaymentsController {
public async charge() {
const response = await secureHttpClient.get('https://api.example.com/charge');
return response;
}
}
These examples ensure that mTLS is used alongside strict hostname validation, mitigating the risk of DNS cache poisoning. Always keep CA bundles up to date and rotate client certificates regularly.