Comment les spammeurs détournent votre site web

Administrateur PHPfrance
Administrateur PHPfrance | 11457 Messages

27 oct. 2006, 20:02

Comment les spammeurs détournent votre site web

De nombreux sites web hébergés sur des serveurs dédiés
se sont récemment retrouvés à diffuser nuit et jour des quantités massives de spam.
Les escrocs profitent pour cela de formulaires web pourtant sécurisés.
Mais en détournant habilement les limites imposées par le webmaster,
ils rendent souvent le serveur inutilisable au quotidien.
Découvrez comment ils s'y prennent.

Attaques et arnaques

Une vague de détournement de sites web frappe actuellement les serveurs dédiés.
Les sites se retrouvent contraints à envoyer des quantités massives de spam,
nuit et jour, jusqu'à devenir quasi-inutilisables pour toute autre tâche.
Certes, la chose n'est pas nouvelle : depuis longtemps déjà,
les spammeurs détournent allègrement les scripts d'envoi d'emails mal sécurisés,
tels ceux qui permettent de stipuler directement l'adresse du destinataire.
Ils s'en prennent même à certains scripts très populaires, tel le tristement célèbre FormMail.pl.
Mais l'originalité de la vague de détournement actuelle,
c'est que les victimes sont des sites dont les webmasters
ont pourtant pris soin de protéger leur script d'envoi de courriers.
L'attaque frappe ainsi des responsables de site a peu près compétents et capables de programmer en PHP.
Lorsque ceux-ci développent un formulaire chargé d'envoyer du courrier
depuis leur site (une page de contact par exemple), ils prennent souvent la peine
de limiter les destinataires à quelques adresses prédéfinies inscrites "en dur" dans le code PHP.
En spécifiant ainsi les seuls destinataires possibles,
ils pensent empêcher l'utilisation du script pour relayer des courriers à n'importe qui.
Mais ils se trompent !

Ajout d'en-têtes à volonté

A l'aide de robots, les spammeurs explorent les sites à la recherche d'un formulaire de contact à abuser.
Peu importe qu'il ne propose pas de saisir directement l'adresse du destinataire.
Ce qui les intéresse, c'est surtout qu'il permette à l'internaute de saisir son adresse email en tant qu'expéditeur.

Une fois un tel formulaire repéré, le spammeur procède à une tentative de détournement :
il utilise le formulaire pour envoyer un courrier tout simple.
Mais après avoir inscrit une adresse quelconque dans le champs "Expéditeur",
il ajoute un caractère de nouvelle ligne ("r" ou "n") suivi, par exemple,
de la mention BCC:, puis d'une adresse email de test.
Lorsque le contenu de ce champs sera inséré dans les headers de l'email construit par le script,
le saut de ligne provoquera la création d'une nouvelle ligne d'en-tête,
qui sera celle que le spammeur a spécifié dans le champs "Expéditeur" du formulaire.
On appelle ça une attaque par injection d'en-têtes, et ça marche très bien !
Si le spammeur reçoit le courrier à l'adresse qu'il a injecté,
c'est que le formulaire peut être utilisé de manière industrielle.
Des outils lui permettront alors d'automatiser la tâche :
il exécutera le script vulnérable directement (sans passer par le formulaire)
et en insérant ses propres en-têtes BCC: garnis de centaines d'adresses emails à spammer.
Et cela nuit et jour.

Mais cette attaque n'est pas utilisable qu'avec les seuls scripts de contact.
Elle permet également de détourner les fonctions d'envoi d'un article à un ami
que l'on retrouve souvent sur les sites d'actualité.
Ces derniers n'autorisent certes pas de soumettre du contenu (puisqu'ils n'envoient que l'article demandé),
mais ils permettent de spécifier l'adresse de l'expéditeur.
Il suffit alors au spammeur, après avoir injecté son champs BCC:
pour définir ses propres destinataires, de créer une nouvelle ligne
afin de spécifier que le courrier est en HTML (Content-Type:text/html).
Il pourra ensuite y placer son contenu, qui apparaîtra avant l'article légitime.

Se protéger efficacement

La solution pour se protéger contre une telle attaque est d'interdire
les sauts et retours de ligne dans les adresse emails fournies par l'utilisateur.
Cela peut se faire en modifiant le code de la fonction mail() dans les sources de PHP
ou, plus simplement, en filtrant correctement les entrées de l'utilisateur (ce qui devrait déjà être une habitude !).

Ainsi une adresse email ne doit pas être composée d'autre chose
que de deux chaînes de caractères usuels (des lettres, des chiffres, le point, le tiret et l'underscore),
séparées par un caractère @ et suivies d'un point
puis enfin d'une nouvelle série de lettres (et uniquement des lettres).
Une telle règle peut facilement être décrite à l'aide d'une expression régulière.
Il suffit alors de créer une fonction "de sécurité" qui sera appellée
pour contrôler chaque adresse avant de la passer au script proprement dit.

Mammouth du PHP | 2937 Messages

27 oct. 2006, 21:16

Salut!

Une autre solution (solution vengeresse) consiste à créer un dossier aspirant les robots des spams et contenant des adresses courriel bidons (dossier exclu de l'indexation pour éviter de pénaliser les robots légitimes). Tout robot et IP surpris dans ce dossier sera enregistré dans une table de la base de données. Et, à la page des formulaires d'envoi de courriel, on vérifie l'IP et le robot utilisés pour tenter d'y accéder: s'ils sont connus des services, on les en empêche en les redirigeant ailleurs (pour les autres, on autorise l'accès et affiche la page).

C'est ce que je fais pour mes projets. :wink:

Eléphant du PHP | 197 Messages

29 oct. 2006, 22:57

merci pour ton dossier... je vais vérifier mes formulaires :evil:

ViPHP
ViPHP | 1380 Messages

30 oct. 2006, 09:01

Le danger est réel mais la parade facile:
http://www.phpfrance.com/forums/voir_su ... ection.php

:wink:
ripat

Theri
Invité n'ayant pas de compte PHPfrance

31 janv. 2007, 11:18

Ma page Mail.php, script réentrant (les valeurs sont passées en POST et si elles n'exisent pas, on affiche le formulaire de saisie)

Code : Tout sélectionner

<?php // Inclusion des fichiers contenant les déclarations de fonctions require_once("./include/Normalisation.php"); require_once("./mail/ControleMail.php"); require_once("./mail/StockeMail.php"); require_once("./mail/AfficheMail.php"); require_once("./mail/EnvoiMail.php"); // Normalisation des entrées HTTP Normalisation(); echo "<h1>Envoi de mail</h1>"; // Si la variable $envoyer existe, des données ont été saisies dans le formulaire if (isset($_POST['envoyer'])) { // Controle des données en entrée if (!ControleMail($_POST)) { // Un problème quelque part ? Il faut réagir ! echo "<p>Quelque chose ne va pas...</p>"; exit; } // On a passé le test : stockage dans la base StockeMail($_POST); // On affiche le texte du mail AfficheMail($_POST); // Envoi du mail EnvoiMail($_POST); } else { // On affiche simplement le formulaire require ("FormMail.html"); } ?>
Ma page ControleMail.php, assez light et des contrôles à rajouter (une petite expression régulière -qui manque ici, pas de temps pour la chercher, en attendant mon truc marche- ne ferait effectivement pas de mal)

Code : Tout sélectionner

<?php // Fonction contrôlant l'entrée de l'application d'email function ControleMail($mail) { // Le sujet en paramètre doit contenir les entrées : destinataire, sujet et message. Vérification :) if (!isSet($mail['expediteur'])) {echo "Pas d'expéditeur !"; return false;} if (!isSet($mail['sujet'])) {echo "Pas de sujet !"; return false;} if (!isSet($mail['message'])) {echo "Pas de message !"; return false;} // On vérifie que les données ne sont pas vides if (empty($mail['expediteur'])) {echo "Expéditeur vide !"; return false;} if (empty($mail['sujet'])) {echo "Sujet vide !"; return false;} if (empty($mail['message'])) {echo "Message vide !"; return false;} // Maintenant on peut/doit également faire des contrôles sur les valeurs attendues : destinataire, sujet & message // Ici je bloque l'injection d'entêtes SMTP en vérifiant la présence de \r ou \n $verif_injection_entete = 'rien'; $exped = $mail['expediteur']; $verif_injection_entete = strstr($exped, '\r'); $verif_injection_entete = strstr($exped, '\n'); if($verif_injection_entete != 'rien') { echo "Injection d'entête ? Envoi refusé !"; return false; } return true; } ?>

Theri le Vorace
Invité n'ayant pas de compte PHPfrance

31 janv. 2007, 11:21

Me suis dit que ça pourrait manquer, on voit d'où vient $envoyer maintenant ;o)

Code : Tout sélectionner

<!-- Formulaire basique pour l'envoi d'un email --> <FORM ACTION='index.php5?page=./mail/Mail.php' METHOD='POST'> <!-- Champ caché pour indiquer que le formulaire a bien été soumis --> <INPUT TYPE='hidden' NAME='envoyer' VALUE='1'> <TABLE> <TR><TH>Mail Expediteur :</TH> <TD><INPUT TYPE='TEXT' SIZE='40' NAME='expediteur'></TD> </TR> <TR><TH>Sujet :</TH> <TD><INPUT TYPE='TEXT' SIZE='40' NAME='sujet'></TD> </TR> <TR><TH>Message :</TH> <TD><TEXTAREA ROWS='20' COLS='40' NAME='message'></TEXTAREA></TD> </TR> <TABLE> <INPUT TYPE='SUBMIT' Value='Envoyer'> </FORM>
Pour le même prix.. :D

Code : Tout sélectionner

* function test_email($email) * { * if(eregi("((^[a-z])(([a-z0-9_]+)|(([a-z0-9_]+)[\.]([a-z0-9_]+)))[@]([a-z0-9]+)[\-]? * ([a-z0-9]+)[\.](([a-z]+)[\.]?([a-z]+)))",$champ)) * { * $resultat = "ok"; * } * else * { * $resultat = "erreur"; * } * return $resultat; * }

Eléphant du PHP | 82 Messages

07 févr. 2007, 17:54

Salut.

Merci Albat pour ce dossier sur les injections par entêtes. Je suis actuellement en stage, et je dois lutter contre l'utilisation des formulaires de la boîte dans laquelle je bosse à des fins de spam (plusieurs milliers de mails envoyés par heures).

La première mesure que j'ai prise à été d'ajouter une question du style "Est-ce du spam ?" à la fin du formulaire avec pour réponse "Oui" (cochée par défaut) ou "Non". Si la réponse est différente de "Non", on bloque directement. Ca a bien marché pendant quelques jours, mais ca a reprit de plus belle par la suite.

Le soucis a été de trouver comment ils s'y prenaient pour continuer à les utiliser. En se servant de l'adresse email ? non le code la vérifiait déjà à l'origine.

Pour vérifier les entêtes envoyées au formulaire j'ai ajouté ce bout de code sur un des formulaires qui en envoyait le plus :
ob_start();
$handle = fopen('./admin/request.html', "a+");
$content = '<pre>';
foreach($_REQUEST as $k => $v)
{
	$content .= "$k -> $v<br />";
}
$content .= "</pre>";
fwrite($handle, $content);
fclose($handle);
Je sais pas si c'est super académique, mais grace à ça j'ai pu m'appercevoir que :
- Le spammeur passait pas par le formulaire : les valeurs de certains champs type radio ayant des valeurs changées par rapport au HTML.
- Le spammeur ne passait pas par le champs email pour faire ses injections mais par le champs "nom".
- Dans toutes les attaques, tous les autres champs étaient remplis avec une adresse email bidon.

Ce que j'ai alors mis en place ces vérifications sur $_POST :
foreach($_POST as $k => $v)
{
	// On regarde qu'il n'y ait pas de saut de ligne dans tous les champs
	// excepté les textarea (ici remarques par exemple).
	if($k != 'remarques')
	{
		if(eregi("\r", $v) || eregi("\n", $v)) die('Erreur détectée, veuillez recommencer !');
	}
	
	// Etant donné que le spammeur remplissait les autres champs avec des adresses emails,
	// on vérifie qu'il n'y ait pas d'adresse mail dans les champs autres que "email" et les textarea
	if($k != 'email' && $k != "remarques")
	{
		if(strpos($v,'@') === false) $spam = $spam;
		else die('Erreur détectée, veuillez recommencer !');
	}
	
	$_POST[$k] = htmlentities($v);
}
Rien qu'avec ça, il y a toujours quelques tentatives d'attaques mais ils n'arrivent plus à passer ces quelques vérifications : ils sont donc bloqués avant l'envoi du mail.

Je me suis pas arrêté là par contre. Le spammeur ne passe pas par le formulaire, donc toutes les limites de taille des champs input (maxlength="xxx") ne servent à rien. Je fais donc une vérification de la taille des champs. Ce qui me donne au final :
foreach($_POST as $k => $v)
{
	// On regarde qu'il n'y ait pas de saut de ligne dans tous les champs
	// excepté les textarea (ici remarques par exemple).
	if($k != 'remarques')
	{
		if(eregi("\r", $v) || eregi("\n", $v)) die("Erreur détectée sur $k, veuillez recommencer !");
		
		// On vérifie que les tailles des champs ne soient pas hors tailles.
		if(strlen($v) > 255) die("Erreur détectée sur $k, veuillez recommencer !");
		// 255 car la plus grosse valeur des différents maxlength est 255
		// Il faudrait peut être que je développe davantage cette partie pour l'adapter au mieux aux différents champs.
	}
	
	// Etant donné que le spammeur remplissait les autres champs avec des adresses emails,
	// on vérifie qu'il n'y ait pas d'adresse mail dans les champs autres que "email" et les textarea
	if($k != 'email' && $k != "remarques")
	{
		if(strpos($v,'@') === false) $spam = $spam;
		else die("Erreur détectée sur $k, veuillez recommencer !");
	}
	
	$_POST[$k] = htmlentities($v);
}
Etant donné qu'un visiteur normal doit passer par le formulaire pour le remplir et l'envoyer, il n'est pas concerné par ces dernières vérifications.

Au final, ça à l'air de marcher : des mails rentrés normalement passent, et toutes les attaques sont bien bloquées.

Désolé d'avoir un peu raconté ma vie, mais les quelques petites observations que j'ai pu faire peux en aider certains qui rencontrent des soucis de spam massif... :wink:

Ah oui, et si vous avez des idées pour améliorer/compléter/corriger mes bouts de codes, je suis preneur.
Sébastien.

Mammouth du PHP | 684 Messages

08 févr. 2007, 16:18

Une autre solution est d'utiliser la fonction de filtre mail de l'extension Filter.
Zigz4g

Eléphant du PHP | 82 Messages

09 févr. 2007, 11:02

Bonjour et merci pour cette fonctionnalité que je ne connaissais pas.

Le soucis c'est que la documentation PHP précise que les fonctions filter ne sont disponibles que depuis PHP5. Vu que je ne dispose que de PHP4 sur les serveurs où sont hébergés ces formulaires, je pourrais pas mettre en place ces fonctions.
Sébastien.