SecureM

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

Optimisez votre code PHP

Bonjour à tous !

C'est après une longue absence que je publie cet article, pour vous donner quelques conseils d'optimisation PHP.

C'est en fait depuis que je me suis remis à mon jeu en ligne futuriste FuturaX que j'avais commencé il y a quelques années, et que je développe seul la seconde version, que je me suis rendu compte de la différence entre mon code de 2004 et mon code d'aujourd'hui, et que je me suis dit que ça valait la peine de vous faire profiter de quelques astuces...

Voici la liste des différences techniques :

  • J'ai changé d'OS pour développer (Win 98 ou 2000 je sais plus, à un Linux Mandriva, mais sur un ordinateur qui ne s'est pas amélioré comme l'OS : 256Mo de RAM et processeur poussif).
  • J'ai changé d'encodage : je passe du ISO-machinchouette-3 à l'UTF-8, j'avais notamment eu un problème dû à l'encodage quand j'ai tenté de passer à l'AJAX (vous ne savez pas ce que c'est ? Pas grave, je vous ferai un article là-dessus si je prends le temps ^^)...
  • J'utilise désormais KDE (avec Linux, désolé pour les gnomes^^) qui permet notamment la gestion transparente du protocole FTP (entre autres), c'est-à-dire que je visualise un dossier distant, j'ouvre une image pour la modifier, je change les propriétés d'un fichier comme si j'étais en local. Cependant comme c'est légèrement plus lent (on s'en doute) et que Olympe-Network (mon génial hébergeur :P ) n'autorise pas plus de 80 utilisateurs connectés en même temps sur le FTP (sur 18000 utilisateurs), ça devenait difficile de développer directement en ligne... J'ai donc installé le serveur Apache et le reste (15 secondes chrono, vive Linux) pour pouvoir développer en local (et je me débarrasse ainsi de tout EasyPHP ou autre pseudo-serveur).
Ce que vous devez retenir, c'est (mis à part que Linux c'est bien - et compliqué) :
  • L'UTF-8 C'EST BIEN C'EST BON MANGEZ-EN !! Sérieusement, c'est beaucoup plus souple, pour quelques modifications pas difficiles : changer l'encodage dans votre éditeur préféré, ajoutez l'en tête HTTP et le meta-tag dans vos pages :
    <?php header('Content-type: text/html; Charset: UTF-8'); ?> (en adaptant bien sûr le content-type - type du contenu)
    Et <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> (de même pour le content-type)
  • Au niveau technique c'est à peu près tout, je ne préfère pas vous donner de conseils trop précis pour ce qui est de l'IDE (environnement de développement) ni de l'OS (gniark gniark ^^)
Pour ce qui est des habitudes de codage, certaines astuces vont sûrement vous intéresser / vous aider :

Manuel des fonctions

Premièrement, n'hésitez pas à ajouter le moteur de recherche du manuel PHP (RTFM qu'on vous dit !) soit en allant directement sur le site http://php.net/ et en ajoutant le moteur de recherche (ça marche sous Firefox du moins) ou bien pour vous simplifier la vie, en cliquant ici pour ajouter directement le moteur de recherche à Firefox.

Vous n'avez alors plus qu'à entrer un nom de fonction, de classe, de ce que vous voulez pour obtenir la documentation complète et les exemples !

Utilisez des classes (et vive la POO ^^)

Deuxièmement, utilisez des classes si cela est pertinent et utile dans le contexte, par exemple pour la gestion de MySQL. C'est ce que j'ai fait, et je peux ainsi centraliser la gestion des erreurs MySQL, la sécurisation des requêtes (contre les injections SQL), ou encore compter le temps mis pour effectuer une requête, le nombre de requête par page en moyenne...

Les expressions ternaires

Utilisez les expressions ternaires, qui fonctionnent de la manière suivante :

$nom = (empty($pseudo)) : 'Inconnu' ? $pseudo;

Elles permettent de remplacer les instructions IF de manière plus réduite, condensée et efficace (sous la forme condition : valeur-si-oui ? valeur-si-non).

L'URL Rewriting

L'URL Rewriting (un article viendra prochainement) permet une gestion beaucoup plus souple des URLs, une petite sécurité supplémentaire, et beaucoup de soucis en moins !L'utilisation des include_once (et require_once) permet d'éviter la double inclusion de code (librairies, configurations, etc...)

Les guillemets simples et doubles

Les guillemets simples sont à préférer aux guillemets doubles dans de nombreux cas. Ce n'est pas une réelle question de rapidité, mais plus de facilité pour vous :
Je rappelle qu'il existe deux manières d'afficher (par exemple) une chaîne de caractères :
echo "Bonjour toi !"; ou echo 'Bonjour toi !';
La différence majeure est que les guillemets doubles permettent d'inclure des variables :
echo "Bonjour "; echo $nom; echo ", ça va ?"; est similaire à echo "Bonjour $nom, ça va ?"; et affichera "Bonjour Camille, ça va ?" si la variable $nom contient Camille.
Au contraire, vous serez obligés de concaténer les morceaux avec les guillemets simples :
echo "Bonjour ",$nom,", ça va ?";
Dans ce cas, les guillemets doubles sont largement préférables, car ils vous permettent de mieux écrire et visualiser vos chaînes formatées, et je vous encourage vivement à les utiliser dans le cas où vous avez des variables à inclure.
Cependant, dans le cas où par exemple vous affichez du HTML, avec de nombreux attributs pour vos balises, je pense qu'il est préférable d'utiliser les guillemets simple pour éviter d'antislasher tous les guillemets doubles du HTML (mais il faudra alors antislasher les guillemets simples). Je m'explique (vous en avez sans doute grand besoin) :
Pour afficher <div class="volante" id="boite_volante"><bold title="Vous avez un nouveau message"><a href="messages.html" title="Afficher ces 2 nouveaux messages">2 Nouveaux Messages !</a></bold></div>, il vaut mieux écrire
echo '<div class="volante" id="boite_volante"><bold title="Vous avez
un nouveau message"><a href="messages.html" title="Afficher ces 2
nouveaux messages">2 Nouveaux Messages
!</a></bold></div>';
que
echo "<div class=\"volante\" id=\"boite_volante\"><bold title=\"Vous avez
un nouveau message\"><a href=\"messages.html\" title=\"Afficher ces 2
nouveaux messages\">2 Nouveaux Messages
!</a></bold></div>";
De même, j'utilise généralement des fonctions du type message($message) appelées par exemple comme message('Une erreur vient de se produire !!"); (enfin celui-ci j'aime pas trop m'en servir :P). Ici l'utilisation de guillemets simples est plus utile.
En règle générale, il vaut mieux faire comme bon vous semble, pour avoir le moins d'anti-slashs à placer dans vos chaînes de caractères.

Journalisation des évènements

Depuis la première version de mon jeu, je journalise les évènements se produisant dans le jeu, au niveau du PHP, comme par exemple, la connexion d'un joueur, chacune de ses actions (construire, attaquer, message...) ainsi que les données techniques (synchronisation de tel ou tel fichier, intrusion, etc...).
Une simple fonction gère cela :

// Enregistrement des actions
function logue($txt){
    // $fic = @file_get_contents(_LOG); // Le fichier n'existe pas forcément
    $txt = '['.date('d/m-H:i').'] '.$txt;
    $fichier = fopen(_LOG,'a+');
    fputs($fichier, " $txt");
    fclose($fichier);
}

// Nettoyage des logs
function nettoielogs(){
    unlink(_LOG);
    logue('Réinitialisation du log...');
}


Où _LOG contient l'adresse interne du fichier.
Ce fichier est envoyé toutes les 24H sur mon adresse mail personnelle et termine dans un dossier spécialisé.

Les raccourcis, les astuces diverses

Connaissez-vous file_get_contents, ou file_put_contents ?
Savez-vous chronométrer le temps d'exécution d'une page, d'une action particulière ?
Un simple appel à microtime et une petite soustraction suffit !

Les optimisations SQL

Ca, c'est un gros chapitre ! J'ai récemment écouté quelqu'un qui comptait stocker ses templates HTML (tout le code HTML autour du contenu) dans une base de données, et qui voulait les récupérer pour chaque page. Il ne connaissait pas la fonction include.
Mon hébergement chez Olympe m'a fait comprendre qu'il était utile - et même important - d'optimiser le volume de données transitant sur le réseau, notamment pour les requêtes SQL.
En utilisant des requêtes plus complètes, plus poussées, on peut faire transiter le strict minimum, et ainsi alléger grandement le pauvre serveur SQL d'Olympe-Network votre propre serveur SQL.
  • Tout d'abord, l'étoile (astérisque) est à proscrire des requêtes :
SELECT * FROM `utilisateurs` est mauvais pour plusieurs raisons :
  1. Vous récupérez TOUS les champs correpondants, même si vous n'en avez pas besoin. C'est souvent près d'1Ko de données par requête par enregistrement, soit beaucoup en 24H pour l'ensemble des enregistrements de l'ensemble de vos bases sur l'ensemble de vos pages appelées par l'ensemble de vos visiteurs... Vous suivez ? ^^
  2. Vous ne connaissez pas la structure de la table que vous appelez ! Généralement je préfère avoir le nom des champs que j'utilise, ça me permet de coder beaucoup plus rapidement.
  • Pour compter le nombre d'enregistrements (nombre de messages, de joueurs, etc...) pensez à utiliser SELECT COUNT(*) AS nb FROM `utilisateurs` qui renvoie pour nb le nombre d'enregistrements (non nuls).
  • Vous pouvez effectuer des sélections croisées, pour cela je ne peux que vous indiquer l'excellent article sur le site du zéro

Voilà, là je suis un peu en manque d'idées, je compléterai sûrement cet article si des choses me reviennent.
J'espère que tout ceci pourra vous être utile =D

TPE : Le piratage informatique

Aujourd'hui, nous avons passé l'oral du TPE, qui compte je vous le rappelle pour le baccalauréat coefficient 2.
Dans notre groupe de quatre, nous avons choisi le piratage informatique comme thème, et joins à ce message le dossier et la présentation que nous avons réalisés.

Au programme : le Hacking (histoire, grands noms, types de hackers), puis les injections SQL, réalisée par Victor B., le phishing, réalisé par Patrick E., puis la Bruteforce, réalisé par Victor A., et pour finir, la faille DNS, que j'ai moi-même présenté.
Nous avons effectuée une démonstration de bruteforce sur les ordinateurs de la salle, où l'examinateur rentrait un mot de passe numérique à 4 chiffres sur le serveur, et un autre ordinateur client le bruteforçait en moins de 4 secondes =D

Le dossier n'est peut-être pas parfait, mais j'espère qu'il pourra vous aider, vous éclairer, ou au moins vous faire découvrir le piratage informatique =D

Télécharger le dossier (PDF)
Télécharger la présentation (Présentation OpenDocument)


Comment parer aux injections SQL

Corriger les failles SQL, c'est assez simple.

  • Il suffit généralement d'appliquer la fonction addslashes() de php à tous les champs qui arrivent dans la requête.
<?php
$pseudo = addslashes($_POST['pseudo']);
$passe = addslashes($_POST['passe']);
mysql_query("SELECT `mess_bienvenue` FROM `utilisateurs`
WHERE `pseudo`='$pseudo' AND `passe`='$passe'",$link);
?>

Cependant cette fonction a ses limites, elle n'échappe que les guillemets simples et doubles, mais ni les anti-slash, ni les caractères NULL.

  • Il existe une autre manière, beaucoup plus simple à mettre en oeuvre, mais qui va disparaître avec PHP 6, il s'agit d'activer les guillemets magiques dans la configuration (fichier php.ini).

Cela échappe les caractères interdits ( sauf % et _ ) et c'est une solution que j'aime bien, elle est activée sur mon hébergeur.

  • Il existe cependant une autre méthode : l'utilisation de la fonction mysql_real_escape_string(). Avec un bon script, on peut pleinement l'exploiter :
<?php

// Annule les effets magic_quotes_gpc/magic_quotes_sybase sur ces variables si ON.

if(get_magic_quotes_gpc()) {
$product_name = stripslashes($_POST['product_name']);
$product_description = stripslashes($_POST['product_description']);
} else {
$product_name = $_POST['product_name'];
$product_description = $_POST['product_description'];
}

// Faire une requête sécurisée
$query = sprintf("INSERT INTO products (`name`, `description`, `user_id`)
VALUES ('%s', '%s', %d)",
mysql_real_escape_string($product_name,$link),
mysql_real_escape_string($product_description,$link),
$_POST['user_id']);

mysql_query($query,$link);

if(mysql_affected_rows($link) > 0) {
echo"Produit inséré ";
}
?>

Mais à ce moment ça se complique un peu. ;)

Vous avez donc le choix entre la fonction addslashes, l'activation des magic-quotes, ou ce que je vous recommande: la fonction mysql_real_escape_string

Les injections SQL en théorie

Dans cet article je vais décrire les injections SQL théoriques. Plus tard nous verrons la pratique, parfois très différente de la théorie.

Le SQL est le langage utilisé pour interagir avec les bases de données. Si vous ne connaissez pas ce langage, je vous conseille d'en apprendre au moins les bases.

Les injections SQL, c'est quoi ? Les injections SQL permettent d'exécuter du code SQL arbitrairement, et ainsi d'agir sur la base de données en visionnant et en modifiant le contenu de celle-ci. Les conséquences peuvent aller jusqu'à la récupération de mots de passe, l'ajout d'un nouvel admin, ou peuvent encore conduire vers une faille PHP.

Les injections SQL, comment ça marche ? Pour permettre une injection SQL, il faut qu'une valeur provienne de l'ordinateur client. Cela est généralement sous la forme d'un formulaire dont l'utilisateur aura malicieusement rempli les champs. Mais les injections peuvent aussi être faites à partir de l'URL, des cookies, ou encore des headers HTTP. Au final, tout ce qui arrive au traitement de la requête SQL et venant de l'ordinateur client peut être considérer comme potentiellement dangereux.

Injection par un champ non protégé L'exemple le plus courant (malheureusement) est celui d'une page de connexion : L'utilisateur est censé donner son identifiant et son mot de passe :

<form action="connexion.php">
Pseudo : <input name="pseudo" /><br />
Passe : <input name="passe" /><br />
<input type="submit" />
</form>

Et voici maintenant le côté PHP :

$pseudo = $_POST['pseudo'];
$passe = $_POST['passe'];
mysql_query("SELECT `mess_bienvenue` FROM `utilisateurs`
               WHERE `pseudo`='$pseudo' AND `passe`='$passe'", $link);

Ainsi, un utilisateur "normal" rentrerait Micky et secret comme identifiants et verrait ainsi son message de bienvenue, dans le cas contraire il arriverait sur une page lui disant que les identifiants sont mauvais et qu'il doit réessayer.

SELECT `mess_bienvenue` FROM `utilisateurs` WHERE `pseudo`='Micky' AND `passe`='secret'

Cependant un utilisateur malicieux pourrait entrer Micky'-- comme identifiant. La requête deviendrait ainsi :

SELECT `mess_bienvenue` FROM `utilisateurs`
                WHERE `pseudo`=' Micky'-- ' AND `passe`='gniark'

Le -- met en commentaire le reste de la ligne et permet ainsi de se connecter quand le pseudo est Micky et c'est tout. Ainsi on peut se connecter avec n'importe quel utilisateur. Il n'est même pas nécessaire de connaître le pseudo de l'admin puisqu'il suffit de faire

WHERE `pseudo`=' 0' or 1=1-- ' AND `passe`='gniark'

Généralement le premier inscrit est le créateur (=administrateur) du site. On se retrouve donc connecté avec les droits administrateurs en un rien de temps ! Il existe aussi d'autres variantes:

WHERE `pseudo`='a' or 'a'='a
WHERE `pseudo`='a' or 1>0

Mais le formulaire de connexion n'est pas la seule zone dangereuse ! Un formulaire de recherche d'utilisateurs par exemple, peut se révéler diabolique. Que pensez vous de ceci :

SELECT `adresse` FROM `utilisateurs` WHERE `nom`='%$recherche%'

Qui pourrait se transformer en

SELECT `adresse` FROM `utilisateurs` WHERE `nom`='%%' order by id-- %'

Ce qui nous affichera la liste complète des utilisateurs. Pas grave, me dites-vous ? Et ceci ?

SELECT `adresse` FROM `utilisateurs` WHERE `nom`='%%' or substring(`passe`,0,1)='a'-- %'

Et alors ? Et bien ça affichera tous les utilisateurs dont le mot de passe commence par un a. Avec quelques connaissances, on arrive ainsi facilement à trouver le mot de passe administrateur, en crypté si vous avez été intelligent. Mais pour le décryptage de MD5, c'est une autre histoire... ;)

Variantes possible Toutes aussi graves, on peut exploiter quelques directives SQL:

LOAD_FILE('/chemin/vers/un/fichier/sensible')

Qui permet de charger un fichier pour remplacer un bout de SQL. Si on fait volontairement planter la requête MySQL, et qu'elle est affichée, on peut ainsi obtenir des fichiers assez sensibles tels que /etc/shadow et autres... Bien sûr si on peut lire un fichier, on peut aussi écrire:

INTO OUTFILE (ou INTO DUMPFILE) /var/www/html/sql.txt

Et le fichier contiendra le résultat de la requête. En y mettant un peu de PHP, on peut réussir un contrôle total... D'où l'intérêt de protéger ses requêtes SQL.