HIGH dangling dnsadonisjsmutual tls

Dangling Dns in Adonisjs with Mutual Tls

Dangling Dns in Adonisjs with Mutual Tls

Dangling DNS occurs when an application resolves a hostname to an IP address that is no longer correct or expected, often because DNS records change after the initial lookup. In AdonisJS, this typically arises when the framework or underlying Node.js DNS resolver caches a DNS result and the target host is later repurposed or replaced. When combined with Mutual TLS (mTLS), the risk profile changes because mTLS requires both client and server to present valid certificates. If the server identity is encoded as a hostname (e.g., in the Common Name or Subject Alternative Name of a certificate), a dangling DNS entry can cause the client to inadvertently connect to a malicious host that presents a valid certificate for the original hostname.

In AdonisJS, mTLS is commonly enforced at the HTTP client level when making outbound requests (e.g., to a payment gateway or internal microservice) or at the server level when terminating TLS. Consider an AdonisJS service that uses the built-in HttpClient to call an internal API secured with mTLS. The client resolves the server hostname once and caches the IP. If the DNS record for that hostname is updated to point to a different server (e.g., during a migration or due to a compromised DNS record), the client may continue to use the stale IP while still validating the server’s certificate. If the new server presents a valid certificate for the original hostname (e.g., through a wildcard or a misissued certificate), the mTLS handshake may succeed, leading to unintended communication with a rogue endpoint.

This scenario is particularly dangerous in environments where certificate authorities are compromised or where wildcard certificates are used. An attacker who can manipulate DNS (e.g., via a cache poisoning attack) could redirect traffic to a server they control, provided they can obtain a valid certificate for the target hostname. In AdonisJS, this can happen if the application does not pin certificates or validate hostnames strictly. The framework does not inherently prevent DNS-based redirection when mTLS is in use, so developers must implement additional safeguards such as certificate pinning or explicit hostname verification.

To illustrate, here is a realistic AdonisJS HTTP client configuration that could be vulnerable to dangling DNS when mTLS is used without additional checks:

const HttpClient = use('HttpClient')

async function callSecureService() {
  const response = await HttpClient.get('https://internal-api.example.com/data', {
    clientCert: '/path/to/client.crt',
    clientKey: '/path/to/client.key',
    ca: '/path/to/ca.crt'
  })
  return response.json()
}

In this example, if internal-api.example.com’s DNS record points to a new server that presents a valid certificate for the same hostname, the request may succeed despite the redirection. The mTLS handshake validates the certificate but does not verify that the hostname matches the expected service endpoint. This gap can be exploited in targeted attacks, especially in supply chain or internal network scenarios.

Mitigating dangling DNS in AdonisJS with mTLS requires a layered approach. Developers should avoid relying solely on DNS-based identity and instead use certificate pinning, strict hostname validation, and short DNS timeouts. The framework’s HTTP client can be extended to enforce these checks, ensuring that even if DNS changes, the connection remains secure.

Mutual Tls-Specific Remediation in Adonisjs

Remediation for dangling DNS in AdonisJS with mTLS focuses on ensuring that the identity of the server is verified beyond just the certificate’s validity. This involves pinning certificates, enforcing strict hostname verification, and minimizing DNS caching. Below are concrete code examples demonstrating secure mTLS configurations in AdonisJS.

1. Certificate Pinning with Hostname Verification

Instead of relying on the default CA bundle, pin the expected server certificate or public key. Combine this with explicit hostname checks to prevent redirection to unintended hosts.

const HttpClient = use('HttpClient')
const tls = require('tls')

async function callSecureServicePinned() {
  const agent = new tls.Agent({
    cert: '/path/to/client.crt',
    key: '/path/to/client.key',
    ca: '/path/to/ca.crt',
    checkServerIdentity: (hostname, cert) => {
      // Enforce hostname match
      if (hostname !== 'internal-api.example.com') {
        throw new Error(`Hostname mismatch: expected internal-api.example.com, got ${hostname}`)
      }
      // Pin server certificate fingerprint (SHA-256)
      const expectedFingerprint = 'A1:B2:C3:D4:E5:F6:...'
      const actualFingerprint = cert.fingerprint.replace(/:/g, '').toUpperCase()
      if (actualFingerprint !== expectedFingerprint) {
        throw new Error('Certificate fingerprint mismatch')
      }
      return undefined // indicates success
    }
  })

  const response = await HttpClient.get('https://internal-api.example.com/data', {
    agent
  })
  return response.json()
}

This approach ensures that even if DNS is manipulated, the connection will fail unless the attacker can present a certificate with the exact pinned fingerprint and matching hostname.

2. Short-Lived DNS Caching and Retry Logic

Reduce the window of opportunity for dangling DNS by minimizing cache duration and implementing retries with fresh lookups.

const HttpClient = use('HttpClient')

async function callWithFreshLookup() {
  const originalLookup = dns.lookup
  dns.lookup = (hostname, options, callback) => {
    // Force fresh DNS resolution by not caching
    originalLookup(hostname, { all: false }, callback)
  }

  try {
    const response = await HttpClient.get('https://internal-api.example.com/data', {
      clientCert: '/path/to/client.crt',
      clientKey: '/path/to/client.key',
      ca: '/path/to/ca.crt',
      timeout: 5000
    })
    return response.json()
  } finally {
    dns.lookup = originalLookup
  }
}

While this example modifies the global DNS lookup temporarily, in production you would prefer using a custom agent or a library that supports per-request DNS resolution.

3. Use of IP Addresses with mTLS (When Applicable)

In highly controlled environments, bypass DNS entirely by connecting directly to an IP address and validating the server certificate against a known hostname.

const HttpClient = use('HttpClient')

async function callSecureServiceByIP() {
  const response = await HttpClient.get('https://192.0.2.1/data', {
    clientCert: '/path/to/client.crt',
    clientKey: '/path/to/client.key',
    ca: '/path/to/ca.crt',
    servername: 'internal-api.example.com' // SNI and hostname verification
  })
  return response.json()
}

Here, the servername option ensures that the TLS handshake validates the certificate against the expected hostname, mitigating dangling DNS even when connecting via IP.

These remediation techniques align with the broader goal of securing mTLS deployments in AdonisJS by addressing the intersection of DNS instability and certificate-based authentication.

Frequently Asked Questions

Can dangling DNS affect mTLS even if certificates are valid?
Yes. Dangling DNS can redirect traffic to a different server that still presents a valid certificate for the expected hostname, allowing unauthorized communication despite valid mTLS.
Does AdonisJS automatically verify hostnames during mTLS handshakes?
No. AdonisJS relies on the underlying Node.js TLS module, which requires explicit configuration for strict hostname verification and certificate pinning to prevent dangling DNS exploits.