Skip to content

Latest research: Read the advisory

All research
CVE AnalysisWeb SecurityCVE-2020-7769 8.6

CVE-2020-7769: command injection in Nodemailer's sendmail transport

An Ngo· Co-Founder4 min read

When a mail library shells out to /usr/sbin/sendmail, attacker-controlled recipient addresses become sendmail command-line flags. This is how CVE-2020-7769 turns an email field into arbitrary file writes, and how prototype pollution upgrades it to full RCE.

Nodemailer is the default way Node.js applications send email. One of its transports invokes the system sendmail binary directly, and CVE-2020-7769 is what happens when the arguments to that binary are not properly separated from user input.

The root cause

The sendmail transport builds a command and runs it through Node's child_process.spawn. The flaw is in how the recipient and options reach that call: user-controlled values are passed to the sendmail process without being safely isolated from its argument list. sendmail, like most Unix tools, treats any argument that starts with - as a flag — so if an attacker can get their string into the argument array, they can inject flags.

Nodemailer's sendmail transport, where user input reaches the spawned process.
The transport shells out to sendmail; the argument boundary is the whole ballgame.

Three ways to exploit it

1. Direct control of `path` and `args`. If the application lets user input reach the SendmailTransport configuration, the attacker simply supplies their own program and arguments:

{
  path: 'ls',
  args: ['-al']
}

Now the "mail transport" runs ls -al instead of sending mail. Anywhere those fields are derived from request data, this is direct arbitrary command execution.

2. Flag injection through the recipient address. The more subtle and more common case: the to field. sendmail accepts flags that change its behaviour, including where it writes output. A crafted recipient such as:

-Dabcas.txt@ethereal.email

is parsed by sendmail as a -D option (write the debug/output file), not as an email address. That gives an attacker a primitive to create or overwrite files on the host, entirely through what looks like a normal "send to" field.

3. Prototype pollution → RCE. On its own, flag injection gives file writes and option abuse. Chained with a prototype pollution bug elsewhere in the application, an attacker can influence the path/args used by spawn through polluted object prototypes and escalate the whole thing to remote code execution. This is the realistic worst case, and why the issue rates as High severity.

Mitigation

  • Upgrade Nodemailer to a fixed release; the patched transport no longer lets recipient values leak into the sendmail argument list as flags.
  • Never let request data flow into transport path or args.
  • Validate email addresses strictly and reject anything beginning with -; terminate sendmail options with -- before recipient arguments.
  • Prefer an SMTP transport over shelling out to a local sendmail binary when you can — it removes the command line from the attack surface entirely.

The through-line with the other bugs on this blog is the same: a trust boundary that nobody labelled as one. A recipient address is user input. The moment it becomes a command-line argument, it is code.

An Ngo

Co-Founder, Starfish Security

LinkedIn