picture picture
juillet 25, 2008 MySQL, PHP 15 Commentaires

Quelques règles de sécurité pour un PHP moins vulnérable…

Il y a les maîtres hackers, ceux qui trouvent les « exploits » en créant des brèches dans les failles des sites. Pour ceux là j’ai du respect, comme tout développeur : ce sont de vrais pros, et leurs travaux font avancer la sécurisation des codes.

Et puis il y a la foule des charognards… tous ces petits informaticiens en culotte courte qui se jettent sur les bouts de codes trouvés sur « Gougueul », en s’affublant de pseudos à la « darkMitnik » ou « KiNg~HaCk » pour faire croire qu’ils savent; et qui passent le plus clair de leurs nuits à casser des bases avec jubilation : pour ceux là je n’ai que du mépris. Ils ne le savent probablement pas (ils sont si jeunes, ces chérubins…), mais ils occasionnent, à peu de risques, des pertes financières sensibles à des gens qui n’ont demandé qu’à vivre honnêtement du fruit de leur juste labeur (comme c’est bien dit !)

C’est pour tenter de mettre quelques barrières simples contre ceux là, après avoir été victime d’un de ces petits bidouilleurs du dimanche, que j’ai tenté de réunir ici quelques éléments de sécurisation dans les applications PHP…

Pour les gens pressés de bâcler leur sécurité, le résumé de cet article est ici !

:)) LES CMS

– La première règle consiste à tenter d’utiliser le moins de CMS possible (SPIP, Joomla, PHPbb, DotClear, eCommerce, etc…)
Par définition ce sont des « Open Source », autrement dit leurs failles de sécurité sont connues de tous.
Si on est « obligé » d’en utiliser un (un blog ou un forum, par exemple, le codage « maison » d’un de ces outils étant excessivement lourd), le règle d’or sera alors de mettre à jour son CMS dès qu’une nouvelle version sort.
Elles sont en général liées à la découverte d’une faille…

– La seconde règle consiste à supprimer les fichiers d’installation des CMS qui contiennent en général en clair les paramètres de connexion à la base…

:)) LES CROSS SITE SCRIPTING PAR FORMULAIRES

Les cross site scripting est le fait d’utilisateurs insérant dans un champ de texte, ou dans une URL, des chaines de caractères incluant du code HTML exécutable.

Par exemple pour un champ de texte demandant un nom d’utilisateur $user, on insérera intégralement :

toto<script type="text/javascript" src="http://www.website.com/malicious.js"></script>

l’affichage du nom de cet utilisateur (echo "Bonjour, $user";) permettra donc l’exécution du script malicious.js…

Pour éviter cela, quelques précautions :

  • Dans les formulaires, limiter la taille de saisie à ce qui est nécessaire (taille d’un mot de passe p.e) quand c’est possible, avec l’attribut « maxlenght » de la balise <input> concernée.
  • Idem sur les champs MYSQL : il est inutile de prévoir un champ de type « text » ou « blob » pour un code postal…
  • Éventuellement, ne pas autoriser autre chose que des chiffres et lettres avec un regex PHP (mais c’est lourd en terme de ressources serveur, ce n’est donc pas toujours idéal)
  • Voir aussi les précautions relatives aux injections SQL, ci-dessous, certaines fonctions PHP peuvent « nettoyer » les variables récupérées.. en encodant tous leurs caractères non héxadécimaux.

:)) LES CROSS SITE SCRIPTING PAR LES FICHIERS INCLUDE()

Une attaque classique consiste à « injecter » du code malicieux, ou à forcer l’affichage de fichiers « sensibles » de votre serveur, par l’intermédiaire d’un fichier externe appelé en « include() » ou « require() » dans votre page PHP.

Si le nom du fichier appelé est sous forme d’une variable, par exemple :

<?php include("files/".$page.".php"); ?> 

alors, une URL cliquable (dans un forum, un mailing, etc…) « trafiquée » de la sorte :

<a href="http://www.votre_site.com/index.php?page=<script>ici le code de mon script tres mechant</script>"> Cliquez ici </a>

déclenchera donc ce script malveillant (upload, redirection, etc…) depuis une page de votre site via le message d’erreur qui va s’afficher :
Warning: Failed opening 'files/<script>ici le code de mon script tres mechant</script>.php' for inclusion (include_path='') in /home/www/index.php on line 1

S’il est nécessaire que le nom du fichier « include » soit une variable, l’une des solutions consiste à toujours vérifier l’existence du fichier externe que l’on appelle avant d’en injecter le code dans la page en cours. Ainsi il n’y aura pas de message d’erreur, donc pas de script malveillant, en cas d’URL non valide :

 <?php $filename = "files/".$page.".php"; if (file_exists($filename))      include($filename); ?>

D’autres solutions consistent à appliquer des règles citées plus bas pour d’autres failles :
– ne pas autoriser le serveur à afficher les erreurs PHP (voir ici, il est possible néanmoins de déboguer temporairement)
– encoder systématiquement les variables echo ou print avec par exemple la fonction htmlentities() de façon à ne pas permettre l’affichage d’un script injecté. Cette méthode est fastidieuse à mon sens… je lui préfère donc les 2 précédentes !
– et encore imposer une extension (comme dans l’exemple ci-dessus) pour éviter les .cgi ou autres éxécutables…

Il est enfin souhaitable d’initialiser toutes les variables utilisées sur une page, de façon à ne pas permettre l’utilisation de variables « injectées ».
Dans l’exemple qui suit, si la variable $authorized n’avait pas été initialisée, un « injection » de « $authorized=1 » (par l’une des méthodes pré-citées), aurait permis de lire la zone cachée :

<?php $authorized = 0; if ($password == "mon_passwd") {    $authorized = 1; } if ($authorized == 1) {   // zone cachée    echo "plein de trucs importants !"; } ?>

:)) LES INJECTIONS DE HEADERS AVEC LA FONCTION MAIL()

Un autre type d’injection consiste à saisir des en-têtes d’e-mails « trafiquées », dans le formulaire d’un site qui utilise la fonction mail() pour traiter les variables d’un formulaire.
Cette technique est régulièrement utilisé par les « spammeurs » pour récupérer des adresses e-mails, ou envoyer des e-mails de spams anonymes depuis le formulaire du site (voir détails ici).
Il est donc souhaitable de « patcher » les formulaires dont les données sont destinées à être envoyés par e-mails, pour supprimer les en-têtes frauduleuses potentielles.
C’est le rôle, par exemple, de la fonction suivante, déposée en tête de la page qui reçoit les variables :

 // vérification que l'utilisateur est humain (ni un script ni un robot) if(!isset($_SERVER['HTTP_USER_AGENT'])){    die("Interdit...");    exit; } if(!$_SERVER['REQUEST_METHOD'] == "POST"){    die("Interdit...");    exit; } // vérification que l'expéditeur n'envoie pas de champs CC: dans le champ expéditeur $from=$_POST["email"]; // personnalisez ici le nom du champ "e-mail" de votre formulaire $from = urldecode($from); if (eregi("
",$from) || eregi(" ",$from)){    die("Caracteres interdits ... :(");    exit; // vérification des chaines d'injections de mails dans les textarea $badStrings = array("Content-Type:", "MIME-Version:", "Content-Transfer-Encoding:", "bcc:", "cc:"); // recherche des badstrings dans toutes les variables POST reçues foreach($_POST as $k => $v){    foreach($badStrings as $v2){       if(strpos($v, $v2) !== false){          echo 'Caracteres interdits ... :(';          exit;       }    } } 

:)) LES INJECTIONS SQL

Cette technique concerne les formulaires en ligne, et consiste en l’insertion de code SQL dans un formulaire, de façon à modifier les requête de traitement des variables saisies, et à déclencher des fonctions indésirables avec la base des données.
Exemple d’ une exemple d’attaque par injection SQL :
Le code d’appel d’une requête MySQL est :

<?php  $query = "SELECT * FROM users WHERE user='{$_POST['username']}' AND password='{$_POST['password']}'";  mysql_query($query); ?>

Si on ne vérifie pas $_POST[‘password’], il peut contenir ce que l’utilisateur veut …
Par exemple :

  • le caractère d’échappement « # » qui arrête la lecture de la requête SQL en cours
  • ou encore $_POST[‘username’] = ‘toto’ et $_POST[‘password’] = « ‘ OR ‘1’=’1 » (ce qui est toujours vrai !)

Cela signifie que la requête envoyée à MySQL permet l’accès libre à la zone « sécurisée » et affiche :
echo $query

On peut aller beaucoup plus loin en injectant du code HTML dans les variables de formulaire (voir ici)…

Pour tenter de parer ceci, il va être nécessaire de ré-encoder les variables récupérées sur les formulaires, pour supprimer les éventuels caractères non hexadécimaux (chiffres et lettres) avec des fonctions du type htmlentities(), htmlspecialchars(), ou en mettant des antislashes ( \ ) devant certains caractères spéciaux avec mysql_real_escape_string() (une connexion MySQL est nécessaire AVANT d’utiliser la fonction mysql_real_escape_string(), sinon, une erreur de niveau E_WARNING sera générée), ou addslahes().
Ils ne seront ainsi plus lisibles en clair, et ne pourront s’exécuter en tant que requête SQL ( » ‘  » s’affichera par exemple dans le code de la requête :  » #039;  » ou  » \’  » selon la fonction utilisée)

Une fonction simple permet de faire cela :

<?php function nettoitou($input){    if(is_array($input)){        foreach($input as $k=>$i){            $output[$k]=nettoitou($i);        }    }    else{        if(get_magic_quotes_gpc()){            $input=stripslashes($input);        }        $output=mysql_real_escape_string($input);    }    return $output; } ?>

Mode d’emploi : si on utilise $_POST, $_GET, $_COOKIE or $_REQUEST, inclure la ligne appropriée dans la page qui récupère les variables d’un formulaire :

<?php  $_POST = nettoitou($_POST);  $_GET = nettoitou($_GET);  $_COOKIE = nettoitou($_COOKIE);  $_REQUEST = nettoitou($_REQUEST); ?>

Le revers de la médaille avec l’utilisation de htmlspecialchars(), c’est que si ces données sont destinées, après leur traitement local, à être insérées dans une base des données, il va parfois falloir ensuite décoder les entités HTML
Pour cela on va utiliser le contraire de htmlspecialchars() : htmlspecialchars_decode(). Malheureusement cette fonction n’est accessible qu’à partir de PHP 5.1.0. Il faudra donc utiliser un clone de cette fonction pour PHP 4 :

<?php if(!function_exists('htmlspecialchars_decode')) {    function htmlspecialchars_decode($string, $quote_style=ENT_COMPAT) {        $search = array('&amp;', '&lt;', '&gt;');        $replace = array('&', '<', '>');        if($quote_style >= ENT_COMPAT) {            $search = '&quot;';            $replace = '"';            if($quote_style == ENT_QUOTES) {                $search = '&#039;';                $replace = "'";            }        }        return str_replace($search, $replace, $string);    } } ?>

La fonction ci-dessus (source) a exactement le même comportement que la fonction officielle portant ce même nom.

Si l’on a utilisé addslashes() ou mysql_real_escape_string(), c’est plus facile à « décoder » grâce a la fonction stripslashes()

:)) EN VRAC, QUELQUES PRÉCAUTIONS RELATIVES AU SERVEUR :

– Pour éviter l’affichage en clair des erreurs de parsing PHP : « error reporting » sur off

« register_globals » sur off pour forcer la vérification les sources des variables utilisées dans les pages (GET, POST, SESSION, etc…)

Typez des variables : vérifiez, quand c’est possible, le format des variables.
Par exemple, si vous attendez un nombre entier : settype($_POST['maVar'], 'int'); if($_POST['maVar'] == 0) {... //erreur ... } qui retournera false en cas de chaine de caractères

– Ne jamais passer d’URL en GET, passer par un htaccess si c’est absolument nécessaire :
index.php?page=contactus.html devient index.php?page=.htpasswd

Mots de passe :

  • Changer le pass/login par défaut de MySQL (‘root’ /  »)
  • Utiliser les MD5 pour les mots de passe,
  • …et penser à changer de temps en temps le pass de l’administrateur, en évitant les chaines trop simples (mélanger chiffres et lettres en utilisant différentes casses)

– Accès a PHPMyAdmin : donner le minimum de droits aux utilisateurs autres que l’administrateur (consulter et ajouter des données : SELECT, INSERT, UPDATE) : si ces login/pass était « diffusé », il ne faut pas que l’indiscret de passage puisse effacer les bases…
De même sur vos propres sites, ne nous connectez jamais sur une base de données en tant que super-utilisateur ou propriétaire de la base. Utilisez toujours un utilisateur adapté, avec des droits très limités.

– Et enfin, comme le « risque zéro » est totalement illusoire… prévoir une remise en route possible des sites avec un back-up de 4 heures maxi !

:)) POUR RÉSUMER…

Avec les variables

  • Patchez les formulaires utilisant une fonction mail()
  • Encodez (fonction nettoitou()) toutes les variables POST ou GET destinées à un affichage immédiat ou à un traitement MySQL
  • Limitez la taille de la saisie dans les champs « input »
  • Vérifiez l’origine (Register_global()) et le typage des variables récupérées avant de les utiliser

Avec les CMS

  • Vérifiez les mises à jour au moins une fois par mois sur les sites des éditeurs

Avec les serveurs

  • N’affichez pas les erreurs de code, en vérifiant que « error reporting » soit bien à « off »
  • Initialisez et tracez toutes vos variables (« register_globals » sur off)
  • Soyez paranoïaques avec les mots de passe

… et tout cela devrait déjà rendre la vie un peu plus dure aux mini-mitniks des boards de Warez :D

:| Voilà… surement très incomplet, mais quelques bases sont là !
Pour finir, un petit site qui permet de comprendre pas mal de choses sur le sujet : PHPsecure

Toutes vos suggestions sont bienvenues en complément de ce post ! :)

15 Responses to “Quelques règles de sécurité pour un PHP moins vulnérable…”

15 Commentaires

  1. webtolosa dit :

    @germaine
    ça ne marche pas le coup du javascript, Germaiiiiiiine :tongue:

  2. germaine dit :

    alert(« hello »)

  3. webtolosa dit :

    Je ne vois pas trop où est le pb, prgasp77 ? Le format texte (suppression de toute balise HTML dans les commentaire) une très bonne technique pour éviter les injections de code et les développeurs de WordPress ont sans doute réfléchi à cela dans ce sens : leur reprocher leur amateurisme serait sans doute bien prétentieux ;)

  4. prgasp77 dit :

    Par exemple … ce système de commentaire est assez mal géré … il supprime tout ce qui est compris entre chevrons () plutot que de les remplacer par leur équivalent html (< et >). Pour un site qui parle de technologie web c’est pas conseillé :D

  5. Neo_Ryu dit :

    Il en existe quelques un d’anlyseur, mais certains ont fermés (GoolagScan du cDc par exemple, l’une des team de hackers parmi les plus connus au monde, qui utilisé le moteur de recherche Google afin d’analyser un site et ses failles parmi les 14 plus connues de souvenirs me semble). Après c’est a double tranchant ces outils…

    Sinon tuto nikel, à peu près tout est passé en revue et permettra peut etre d’ouvrir les yeux a certains… (70% des sites ont au moins l’une de ses failles… :\ )

  6. webtolosa@sam93 dit :

    pas à ma connaissance …

  7. sam93 dit :

    y a t’il un analyseur qu detecte ces failles??,

  8. papa6 dit :

    Bonjour,

    Merci pour toutes ces infos très bien.

    Juste une petite remarque : avant de devenir "maître hacker", comme tu les appelles, il faut passer par l’étape en culotte courtes ;)

    Ensuite, c’est -malheureusement- parce qu’il y a ces débutants qui "cassent" du code que je dois perfectionner mes sites. C’est malheureux à dire, mais s’ils n’étaient pas là, on ferait encore plus preuve de fainéantise et nos sites ne bénéficieraient même pas des bases de protection nécessaires. La "sécurité", c’est un devoir pour celui qui prétend programmer sérieusement. Si des bidouilleurs "en culotte courte" lui cassent son code, c’est que ces "boutonneux" sont meilleurs que nous…

  9. Cavey dit :

    Mais est-ce que d’interdir les caractères propre à l’HTML, au SQL et au Javascript ne suffirait pas ?

    En traitant chaque variable et en supprimant les ><et les " ?

  10. Camus Dreck dit :

    Je connaissait déjà mais j’avais complètement zappé cela dans ma façon de codée.
    De plus je ne pensé pas que les risques pouvait être aussi important.

    Merci du rappel à l’ordre :)) :] :)

  11. Moi dit :

    lool :bravo: :C :* ~:( 0) :non: :bravo:

  12. extra dit :

    thx merci pour cet article parfaitement éclairant

  13. bodycatch dit :

    pas mal, c’est assez clair et concis

  14. Garou dit :

    ~:( wouaouhhh… tu fais peur, là… j’avais jamais aussi bien compris les risques de PHP, je sens que je vais changer quelques trucs dans ma façon de coder.
    merci l’ami thx

Commentaire

Name

Mail (ne sera pas publié)

Website

Laisser ces deux champs tels quels :
:D :-) :( :o 8O :? 8) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: