IT Security 1 month ago

Nodemailer DNS Cache Vulnerability – SMTP Credential Disclosure in Multi‑Tenant Environments

A critical vulnerability in Nodemailer's process‑global DNS cache can expose SMTP credentials in multi‑tenant environments. This advisory explains the flaw, affected versions, detection methods, and immediate mitigation steps. A critical vulnerability in Nodemailer's process‑global DNS cache can expose SMTP credentials in multi‑tenant environments. This advisory explains the flaw, affected versions, detection methods, and immediate mitigation steps.

IT Security

Executive Summary

A critical vulnerability in Nodemailer’s process-global DNS caching mechanism allows attackers to intercept SMTP credentials in multi-tenant environments. The flaw stems from improper cache key construction that fails to account for TLS servername values, enabling attackers to manipulate DNS resolution and redirect victim credentials to attacker-controlled servers. All versions from 5.0.0 and above are affected. Organizations using Nodemailer in shared hosting or multi-tenant architectures should prioritize immediate patching and credential rotation.


What You Need to Know About This Vulnerability

How the DNS Cache Flaw Works

Nodemailer maintains a process-global DNS cache designed to improve performance by storing resolved hostnames. However, this cache uses only the host parameter as its key, while storing the associated TLS servername value alongside it. When multiple SMTP transports reference the same DNS host but specify different servername values, the first transport’s server name persists in the cache and gets reused by subsequent transports.

This design flaw creates a critical security gap: a malicious tenant can prime the DNS cache with their own server name, causing legitimate tenants’ SMTP connections to use the wrong SNI (Server Name Indication) value. The victim’s TLS handshake then succeeds against the attacker’s certificate, and the victim’s SMTP credentials are transmitted to the attacker’s server.

Why Multi-Tenant Environments Are at Highest Risk

Multi-tenant architectures—where multiple customers or users share the same application instance—face elevated risk because:

  • Shared Process Space: All tenants’ Nodemailer transports operate within the same Node.js process, sharing the same DNS cache
  • Attacker Control: Malicious tenants can deliberately create transports with specific servername values to poison the cache
  • Credential Exposure: SMTP credentials are typically tenant-specific and highly sensitive
  • Difficult Detection: Cache poisoning attacks leave minimal audit trails and may go unnoticed

Single-tenant applications and those using separate processes per tenant have significantly lower exposure.

Real-World Attack Scenarios

Scenario 1: SaaS Email Platform Compromise

A multi-tenant SaaS platform allows customers to configure custom SMTP servers. Attacker Tenant A creates a transport pointing to mail.attacker.com with servername: "attacker.com". This primes the DNS cache. When Victim Tenant B attempts to send email through their configured SMTP server (mail.victim.com with servername: "victim.com"), the cached attacker servername is reused. The victim’s connection succeeds against the attacker’s certificate, and credentials are compromised.

Scenario 2: Shared Hosting Provider Attack

A hosting provider runs multiple customer applications on shared infrastructure. An attacker customer creates a Nodemailer transport with a malicious servername. When other customers’ applications attempt to send transactional emails, their SMTP credentials are intercepted, enabling account takeover and email spoofing.


Affected Versions and Products

Nodemailer Versions 5.0.0 and Above

Confirmed Vulnerable Versions:

  • 5.0.0 (initial introduction of DNS caching)
  • 10.0.1 (latest confirmed vulnerable in runtime testing)
  • All intermediate versions from 5.0.0 through the latest unpatched release

Affected Ecosystem:

Identifying If Your Application Is Vulnerable

Your application is at risk if:

  1. You use Nodemailer version 5.0.0 or later
  2. Your application operates in a multi-tenant environment
  3. Multiple tenants or users can create or configure SMTP transports
  4. Tenants share the same Node.js process (not isolated in separate processes)
  5. Different tenants use different SMTP servers with different servername values

Checking Your Current Nodemailer Version

Via package.json:

{
  "dependencies": {
    "nodemailer": "^6.9.0"
  }
}

Via command line:

npm list nodemailer

Programmatically in Node.js:

const nodemailer = require('nodemailer');
const pkg = require('nodemailer/package.json');
console.log('Nodemailer version:', pkg.version);

Technical Breakdown of the Attack

How Attackers Prime the DNS Cache

The attack begins with cache initialization. When Attacker Tenant A creates an SMTP transport, Nodemailer’s code executes:

// Simplified Nodemailer DNS cache logic (vulnerable)
const dnsCache = {};
function getConnection(host, servername) {
  if (!dnsCache[host]) {
    dnsCache[host] = {
      resolvedIP: dns.resolve(host),
      servername: servername  // Cache stores servername
    };
  }
  return dnsCache[host];
}

The attacker creates a transport with:

const transport = nodemailer.createTransport({
  host: 'mail.shared.com',
  port: 465,
  secure: true,
  tls: {
    servername: 'attacker.com'  // Attacker's servername
  }
});

This single call populates dnsCache['mail.shared.com'] with the attacker’s servername value.

The Servername Substitution Mechanism

When Victim Tenant B later creates their transport:

const victimTransport = nodemailer.createTransport({
  host: 'mail.shared.com',
  port: 465,
  secure: true,
  tls: {
    servername: 'victim.com'  // Victim's intended servername
  }
});

Nodemailer retrieves the cached entry for mail.shared.com, which already contains servername: 'attacker.com'. The victim’s explicitly configured servername: 'victim.com' is ignored, and the attacker’s value is used instead.

How Credentials Reach the Wrong Server

The TLS handshake proceeds with the wrong SNI value:

  1. Victim’s Transport initiates connection to mail.shared.com
  2. DNS Resolution returns the legitimate IP (from cache)
  3. TLS Handshake sends SNI: attacker.com (from cache, not victim’s config)
  4. Attacker’s Server (listening on that IP or via DNS hijacking) presents a certificate for attacker.com
  5. Certificate Validation succeeds because the SNI matches the certificate
  6. SMTP Authentication proceeds, and victim’s credentials are transmitted to the attacker
Technical flow diagram showing credential interception through DNS cache manipulation
Technical flow diagram showing credential interception through DNS cache manipulation

Immediate Actions to Take

Upgrade Nodemailer to the Patched Version

Priority: Critical

Obtain the latest patched version from the Nodemailer repository:

npm update nodemailer
# or
npm install nodemailer@latest

Verify the update:

npm list nodemailer

For Production Environments:

  • Test the patch in a staging environment first
  • Plan a maintenance window for deployment
  • Coordinate with your DevOps team to ensure zero-downtime deployment if possible
  • Document the update in your change management system

Audit Your SMTP Configurations

Review all SMTP transports in your codebase:

# Search for Nodemailer transport creation
grep -r "createTransport" --include="*.js" .
grep -r "nodemailer" --include="*.js" .

For each transport, document:

  • The host value
  • The tls.servername value
  • Which tenant or user owns the transport
  • Whether multiple transports share the same host

Configuration Audit Checklist:

  • [ ] Identify all Nodemailer transports in your application
  • [ ] Map transports to tenants/users
  • [ ] Verify each transport has an explicit tls.servername value
  • [ ] Confirm servername values match the actual SMTP server hostname
  • [ ] Document any shared hosts across different tenants

Review Multi-Tenant Architecture for Exposure

Assess your application’s isolation model:

High Risk:

  • Single Node.js process serving all tenants
  • Shared SMTP host across multiple tenants
  • Tenants can create or modify transport configurations

Medium Risk:

  • Single process but isolated SMTP credentials per tenant
  • Tenants cannot modify transport configurations

Low Risk:

  • Separate Node.js process per tenant
  • Isolated SMTP infrastructure per tenant
  • Strict tenant isolation at the application level

Detection and Monitoring Strategies

Code Review Checklist for DNS Caching

Conduct a security-focused code review of your Nodemailer usage:

Review Items:

  • [ ] Verify Nodemailer version is up-to-date
  • [ ] Confirm all transports explicitly set tls.servername
  • [ ] Check that servername values are tenant-specific and immutable
  • [ ] Ensure transports are not created dynamically from user input
  • [ ] Validate that SMTP credentials are not logged or exposed
  • [ ] Review error handling for TLS failures

Code Review Example:

// VULNERABLE: servername not explicitly set
const transport = nodemailer.createTransport({
  host: 'mail.example.com',
  port: 465,
  secure: true,
  auth: { user, pass }
});
// SECURE: servername explicitly configured
const transport = nodemailer.createTransport({
  host: 'mail.example.com',
  port: 465,
  secure: true,
  tls: {
    servername: 'mail.example.com'  // Explicit and verified
  },
  auth: { user, pass }
});

Network Monitoring for Suspicious SNI Values

Implement network-level detection:

Monitor Outgoing SMTP Connections:

# Using tcpdump to capture TLS handshakes
tcpdump -i eth0 'tcp port 465 or tcp port 587' -w smtp_traffic.pcap
# Analyze with tshark
tshark -r smtp_traffic.pcap -Y tls.handshake.extensions_server_name \
  -T fields -e tls.handshake.extensions_server_name

Detection Indicators:

  • SNI value differs from the destination hostname
  • Unexpected servername values in SMTP connections
  • Connections to unauthorized SMTP servers
  • Multiple different servername values for the same host

SIEM Integration:
Log and alert on:

  • TLS handshake failures with certificate mismatches
  • Unexpected SNI values in outgoing connections
  • SMTP authentication failures followed by successful connections to different servers

Penetration Testing Approaches

Test Scenario 1: DNS Cache Poisoning Simulation

// Attacker tenant creates transport
const attackerTransport = nodemailer.createTransport({
  host: 'mail.shared.com',
  port: 465,
  secure: true,
  tls: { servername: 'attacker.com' }
});
// Victim tenant creates transport
const victimTransport = nodemailer.createTransport({
  host: 'mail.shared.com',
  port: 465,
  secure: true,
  tls: { servername: 'victim.com' }
});
// Test: Verify victim's SNI value in actual connection
// Expected: victim.com
// Vulnerable: attacker.com

Test Scenario 2: Multi-Tenant Isolation Testing

  • Create multiple tenant transports with different servername values
  • Monitor which servername is actually used in TLS handshakes
  • Verify credentials are sent to the correct server
  • Attempt to intercept credentials through cache manipulation

Test Scenario 3: Credential Interception Lab

Set up a controlled lab environment:

  1. Deploy a test application with vulnerable Nodemailer
  2. Create two tenant transports pointing to the same host
  3. Set up an attacker-controlled SMTP server
  4. Attempt to intercept victim credentials through cache poisoning
  5. Document findings and verify patch effectiveness

Immediate Actions to Take

Upgrade Nodemailer to the Patched Version

Priority: Critical

Obtain the latest patched version from the Nodemailer repository:

npm update nodemailer
# or
npm install nodemailer@latest

Verify the update:

npm list nodemailer

For Production Environments:

  • Test the patch in a staging environment first
  • Plan a maintenance window for deployment
  • Coordinate with your DevOps team to ensure zero-downtime deployment if possible
  • Document the update in your change management system

Audit Your SMTP Configurations

Review all SMTP transports in your codebase:

# Search for Nodemailer transport creation
grep -r "createTransport" --include="*.js" .
grep -r "nodemailer" --include="*.js" .

For each transport, document:

  • The host value
  • The tls.servername value
  • Which tenant or user owns the transport
  • Whether multiple transports share the same host

Configuration Audit Checklist:

  • [ ] Identify all Nodemailer transports in your application
  • [ ] Map transports to tenants/users
  • [ ] Verify each transport has an explicit tls.servername value
  • [ ] Confirm servername values match the actual SMTP server hostname
  • [ ] Document any shared hosts across different tenants

Review Multi-Tenant Architecture for Exposure

Assess your application’s isolation model:

High Risk:

  • Single Node.js process serving all tenants
  • Shared SMTP host across multiple tenants
  • Tenants can create or modify transport configurations

Medium Risk:

  • Single process but isolated SMTP credentials per tenant
  • Tenants cannot modify transport configurations

Low Risk:

  • Separate Node.js process per tenant
  • Isolated SMTP infrastructure per tenant
  • Strict tenant isolation at the application level

Long-Term Mitigation Strategies

Implementing Custom DNS Caching with Servername Keys

If you cannot immediately upgrade, implement a custom DNS cache that includes servername in the cache key:

const dns = require('dns').promises;
class SecureDNSCache {
  constructor() {
    this.cache = new Map();
    this.ttl = 3600000; // 1 hour
  }
  // Cache key includes both host AND servername
  getCacheKey(host, servername) {
    return `${host}:${servername}`;
  }
  async resolve(host, servername) {
    const key = this.getCacheKey(host, servername);
    const cached = this.cache.get(key);
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.ip;
    }
    const ip = await dns.resolve4(host);
    this.cache.set(key, {
      ip: ip[0],
      timestamp: Date.now()
    });
    return ip[0];
  }
}
// Usage with Nodemailer
const dnsCache = new SecureDNSCache();
const transport = nodemailer.createTransport({
  host: 'mail.example.com',
  port: 465,
  secure: true,
  tls: {
    servername: 'mail.example.com',
    lookup: async (hostname, options, callback) => {
      try {
        const ip = await dnsCache.resolve(hostname, 'mail.example.com');
        callback(null, ip, 4);
      } catch (err) {
        callback(err);
      }
    }
  }
});

Tenant Isolation Best Practices

Process-Level Isolation:

// Use worker threads or child processes per tenant
const { Worker } = require('worker_threads');
function createTenantWorker(tenantId, config) {
  const worker = new Worker('./tenant-worker.js');
  worker.on('message', handleMessage);
  worker.postMessage({ tenantId, config });
  return worker;
}

Credential Management:

  • Store SMTP credentials in a secure vault (HashiCorp Vault, AWS Secrets Manager)
  • Rotate credentials regularly
  • Use tenant-specific credentials, never shared
  • Implement least-privilege access for SMTP accounts

Transport Isolation:

// Create transports in isolated contexts
class TenantEmailService {
  constructor(tenantId, config) {
    this.tenantId = tenantId;
    this.transport = nodemailer.createTransport({
      host: config.smtpHost,
      port: config.smtpPort,
      secure: true,
      tls: {
        servername: config.smtpHost,
        rejectUnauthorized: true
      },
      auth: {
        user: config.smtpUser,
        pass: config.smtpPass
      }
    });
  }
  async sendMail(mailOptions) {
    // Validate sender is within tenant's domain
    if (!mailOptions.from.endsWith(this.tenantId)) {
      throw new Error('Invalid sender for tenant');
    }
    return this.transport.sendMail(mailOptions);
  }
}

TLS Configuration Hardening

Enforce Strict TLS Validation:

const transport = nodemailer.createTransport({
  host: 'mail.example.com',
  port: 465,
  secure: true,
  tls: {
    servername: 'mail.example.com',
    rejectUnauthorized: true,  // Enforce certificate validation
    minVersion: 'TLSv1.2',      // Require TLS 1.2 or higher
    ciphers: 'HIGH:!aNULL:!MD5' // Use strong ciphers only
  }
});

Certificate Pinning (Advanced):

const fs = require('fs');
const tls = require('tls');
const transport = nodemailer.createTransport({
  host: 'mail.example.com',
  port: 465,
  secure: true,
  tls: {
    servername: 'mail.example.com',
    ca: [fs.readFileSync('./smtp-server-cert.pem')],
    rejectUnauthorized: true
  }
});

Regular Security Audits:

  • [ ] Quarterly review of TLS configurations
  • [ ] Annual penetration testing of email infrastructure
  • [ ] Continuous monitoring of certificate validity
  • [ ] Automated alerts for TLS handshake failures

Risk Assessment

Severity Evaluation

CVSS v3.1 Estimated Score: 8.1 (High)

Attack Vector: Network
Attack Complexity: Low
Privileges Required: Low (attacker needs tenant account)
User Interaction: None
Scope: Changed (affects other tenants)
Confidentiality Impact: High (SMTP credentials exposed)
Integrity Impact: Low
Availability Impact: None

Impact by Environment Type

Environment Risk Level Impact
Single-tenant application Low Minimal; upgrade recommended
Multi-tenant SaaS Critical Credential disclosure, account takeover
Shared hosting Critical Cross-customer credential theft
Enterprise with process isolation Medium Reduced risk if tenants isolated

FAQ

What is Nodemailer and why is it widely used?

Nodemailer is a popular Node.js module that simplifies email sending for applications. It provides a straightforward API for configuring SMTP connections, handling attachments, and managing email delivery. Its ease of use, reliability, and active maintenance make it a standard choice for developers building email functionality in Node.js applications, from transactional emails to marketing campaigns.

How does this vulnerability specifically affect my application?

If your application uses Nodemailer in a multi-tenant environment where multiple users or customers share the same SMTP host, attackers could intercept SMTP credentials from other tenants through DNS cache manipulation. An attacker with tenant access can prime the DNS cache with their own server name, causing legitimate tenants’ SMTP connections to use the wrong SNI value. This allows the attacker to intercept credentials and gain unauthorized access to email accounts.

Which versions of Nodemailer are vulnerable?

All versions from 5.0.0 and above are affected. Versions 5.0.0 and 10.0.1 have been confirmed vulnerable in runtime testing. The vulnerability was introduced when DNS caching was first implemented in version 5.0.0 and persists through all subsequent versions until patched.

What should I do if I’m using an affected version?

Immediately upgrade to the latest patched version of Nodemailer using npm update nodemailer. After upgrading, audit your SMTP configurations to ensure each transport has an explicit tls.servername value, and review your application architecture for potential credential exposure. If you operate a multi-tenant environment, also assess your tenant isolation model and consider implementing additional security controls.

Is this vulnerability only a concern for multi-tenant applications?

Yes, the vulnerability primarily affects multi-tenant environments where multiple tenants or users share the same Node.js process and can create or configure SMTP transports. Single-tenant applications have significantly lower risk because there is no opportunity for one user to poison the DNS cache for another. However, upgrading is still recommended as a security best practice.

How can I detect if this vulnerability has been exploited?

Monitor outgoing SMTP connections for unexpected SNI values that differ from the destination hostname. Review logs for connections to unauthorized SMTP servers or authentication failures followed by successful connections elsewhere. Conduct penetration testing to simulate the attack scenario and verify your security posture. Implement network monitoring to detect TLS handshake anomalies and certificate mismatches.

Can I implement a workaround without upgrading?

Yes, you can implement a custom DNS caching mechanism that includes the servername as part of the cache key, preventing different tenants’ servername values from colliding. Alternatively, you can isolate tenants to separate Node.js processes, preventing shared access to the same DNS cache. However, these are temporary mitigations—upgrading to the patched version is the recommended long-term solution.

What credentials are at risk?

SMTP authentication credentials (username and password) are the primary risk. These credentials grant access to email accounts and can be used for unauthorized email sending, account takeover, and further exploitation. In some cases, API keys or OAuth tokens used for SMTP authentication may also be exposed.

How should I rotate credentials after this vulnerability?

  1. Identify all SMTP accounts that may have been exposed
  2. Change passwords for all affected SMTP accounts
  3. Review email account activity logs for unauthorized access
  4. Revoke and reissue any API keys or OAuth tokens
  5. Monitor accounts for suspicious activity for 30 days post-rotation
  6. Notify affected users if their accounts were potentially compromised

Are there any known exploits in the wild?

As of the publication of this advisory, there are no confirmed reports of active exploitation in the wild. However, the relative simplicity of the attack and the widespread use of Nodemailer mean that exploitation is likely if the vulnerability remains unpatched. Prioritize patching immediately.


References


Advisory Published: [Current Date] Last Updated: [Current Date] Classification: Security Advisory – Critical
Distribution: Public

For questions or additional information, contact your security team or the Nodemailer maintainers through the official GitHub repository.

← Back to IT Security