Les injections SQL en PHP et TYPO3

Posté le 21-04-2010

Après une expertise récente d’un code source, j’ai eu envie de publier un article de rappel sur les injections SQL en PHP et TYPO3. Les injections SQL sont les failles les plus répandues et les plus dangereuses en PHP. Si on ne protège pas correctement son code, il est possible pour le « pirate » d’accéder à toutes les données contenues dans la base de données. Pour savoir ce qu’est globalement une injection SQL, faisons un petit tour sur wikipedia :

Une injection SQL est un type d’exploitation d’une faille de sécurité d’une application interagissant avec une base de données, en injectant une requête SQL non prévue par le système et pouvant compromettre sa sécurité.

En gros cela consiste à placer une requête SQL dans l’url de votre site web afin de faire ressortir des données « sensibles » au sein de la page. On distingue d’une manière générale 2 types d’injections SQL.

Injection dans les variables de type chaine de caractères

Pour illustrer cette injection rien de mieux qu’un exemple concret et connu de tous : une requête récupérant une personne en fonction de son nom :

$requete = mysql_query("SELECT nom FROM personnes WHERE nom='".$_GET['nom']."'");

Dans ce cas l’url ressemble à cela :

http://www.monsite.fr/index.php?nom=yohann

Pour effectuer une injection SQL dans ce cas il suffit d’écrire (à encoder dans l’url) :

http://www.monsite.fr/index.php?nom=' UNION ALL SELECT password FROM personnes WHERE uid=1--

Pour injecter la requête suivante :

' UNION ALL SELECT password FROM personnes WHERE uid=1--

soit au final :

SELECT nom FROM personnes WHERE nom='' UNION ALL SELECT password FROM personnes WHERE uid=1--

On injecte la requête afin de faire ressortir dans le contenu de la page le « password » de l’utilisateur courant. Cette requête est écrite à titre d’exemple, on peut imaginer énormément de manière différente de la construire (le — permet de commenter tout le reste de la requête effectuée). La seule contrainte est d’afficher autant de champs que ceux sélectionnés dans la requête. Exemple, ici je ne sélectionne que « password » qui s’affichera à la place de « nom ». Si j’avais eu deux champs, nous aurions sélectionné 2 fois le champ password, etc…

Pour éviter ce type d’injection, il suffit d’utiliser la fonction mysql_real_escape_string() de la manière suivante :

$requete = mysql_query('SELECT nom FROM personnes WHERE nom="'.mysql_real_escape_string($_GET['nom']).'");

Cela a pour effet de transformer la requête injectée en :

\' UNION ALL SELECT password FROM personnes WHERE uid=1--

Et du coup, la requête ne passe plus. Ce qui entraine forcément un manque de données dans la page. Pour cela, à vous de gérer les cas d’erreurs.

Injection dans les variables de type entier

Pour illustrer cette injection rien de mieux qu’un exemple concret et connu de tous : une requête récupérant une personne en fonction de son id :

$requete = mysql_query('SELECT nom FROM personnes WHERE uid='.$_GET['uid']);

Dans ce cas l’url ressemble à cela :

http://www.monsite.fr/index.php?uid=1

Pour effectuer une injection SQL dans ce cas il suffit d’écrire (à encoder dans l’url) :

http://www.monsite.fr/index.php?uid=2 UNION ALL SELECT password FROM personnes WHERE uid=1--

Pour injecter la requête suivante :

2 UNION ALL SELECT password FROM personnes WHERE uid=1--

soit au final :

SELECT nom FROM personnes WHERE uid=2 UNION ALL SELECT password FROM personnes WHERE uid=1--

On injecte la requête afin de faire ressortir dans le contenu de la page le password de l’utilisateur courant. Mêmes remarques que la partie suivante…

Pour éviter ce type d’injection, il suffit d’utiliser la fonction intval() de la manière suivante :

$requete = mysql_query('SELECT nom FROM personnes WHERE uid='.intval($_GET['uid']));

Cela a pour effet de transformer la requête injectée en entier. Et dans ce cas, comme on lui passe une chaine, cela ne renvoie rien. Mon code est simplifié au maximum pour l’exemple et le mieux est de diviser les récupérations de $_GET/$_POST dans des variables afin de vérifier qu’elles sont bien renseignées et du bon type.

Remarque sur l’utilisation de t3lib_div::_GP sous TYPO3

J’ai souvent entendu que cette fonction permettait d’éviter les injections SQL. En aucun cas, l’utilisation de cette méthode empêche l’injection SQL. En voici son contenu :

public static function _GP($var)  {
    if(empty($var)) return;
    $value = isset($_POST[$var]) ? $_POST[$var] : $_GET[$var];
    if (isset($value))  {
        if (is_array($value)) { t3lib_div::stripSlashesOnArray($value); } else { $value = stripslashes($value); }
    }
    return $value;
}

Cette méthode est une fusion de $_GET et $_POST et retire les magic_quotes (si elles sont actives sur votre serveur). Elle n’est donc en aucun cas obligatoire. L’injection fonctionne donc toujours même si on utilise cette fonction. Il est indispensable que le développeur gère lui même toutes les variables envoyées.

Voila ce que je souhaitais dire sur les injections SQL. Je suis ouvert à toutes remarques afin d’étoffer un peu mon article et surtout à discuter sur les bonnes pratiques.

comments powered by Disqus