Email parser discrepancy in Nodemailer

TL;DR

After reading Splitting the Email Atom research, I set out to find a zero-day bug in Nodemailer for one of my CTF challenges. I discovered that Nodemailer was not correctly parsing certain valid email addresses. Consider this example:

"test@0xhexr4.htb"@google.com

According to RFC 5322, this is a valid email address. The portion "test@0xhexr4.htb" is a quoted local-part, which allows the @ character. The domain is google.com. Thus, the mailbox is "test@0xhexr4.htb" at the domain google.com.

However, Nodemailer’s parser attempts to identify the address by splitting at the first @ found outside of a properly recognized quoted local-part. Here’s a snippet (taken from Nodemailer’s code) showing how it separates tokens into different lists:

data = {
    address: [],
    comment: [],
    group: [],
    text: []
};

When Nodemailer encounters "test@0xhexr4.htb"@google.com, it correctly identifies the quoted string "test@0xhexr4.htb" as the local-part, but then treats the subsequent @google.com segment as non-quoted “text.” It fails to recognize that google.com is the domain. Instead, it might parse the input as:

[{
    name: "@google.com",
    address: "test@0xhexr4.htb"
}]

This results in Nodemailer interpreting "test@0xhexr4.htb" as the entire mailbox and @google.com as a display name, effectively losing the actual domain information (google.com).

I reported this to the Nodemailer team, hoping it might be treated as a security issue, since incorrect parsing could lead to emails being sent to the wrong domain. However, they considered it a regular bug rather than a security flaw. They fixed this issue in version 6.9.16 without providing credit.


Nodemailer Zero Day


Accepting their stance, I moved on and found another problem. I found out that it is still vulnerable to the same kind of issue. For example, consider this payload:

"test@email.htb x"@interstellar.htb

It exhibits similar behavior, and the email is sent to the test mailbox on email.htb rather than interstellar.htb.

If we parse the following email using a library such as Email-Addresses


Welcome to Node.js v23.3.0.
Type ".help" for more information.
> addrs = require("email-addresses")
[Function: parse5322] {
    parseOneAddress: [Function: parseOneAddressSimple],
    parseAddressList: [Function: parseAddressListSimple],
    parseFrom: [Function: parseFromSimple],
    parseSender: [Function: parseSenderSimple],
    parseReplyTo: [Function: parseReplyToSimple]
}
> addrs.parseOneAddress(`"test@email.htb x"@interstellar.htb`)
{
    parts: {
    name: null,
    address: {
        name: 'addr-spec',
        tokens: '"test@email.htb x"@interstellar.htb',
        semantic: 'test@email.htb x@interstellar.htb',
        children: [Array]
    },
    local: {
        name: 'local-part',
        tokens: '"test@email.htb x"',
        semantic: 'test@email.htb x',
        children: [Array]
    },
    domain: {
        name: 'domain',
        tokens: 'interstellar.htb',
        semantic: 'interstellar.htb',
        children: [Array]
    },
    comments: []
    },
    type: 'mailbox',
    name: null,
    address: 'test@email.htb x@interstellar.htb',
    local: 'test@email.htb x',
    domain: 'interstellar.htb',
    comments: '',
    groupName: null
}
>

The domain is "interstellar.htb". I haven't reported it because it’s considered a normal bug :)

The latest version as for now v6.9.16 is still vulnerable to this. I showcased this second bug in the University CTF 2024 Intergalactic Bounty web challenge. Despite the Nodemailer team’s response to the first bug, I hope that highlighting these issues will help raise awareness about how subtle parsing errors can lead to unexpected behavior.