Détournement d'un formulaire d'inscription

Dans cet exemple, nous allons voir comment une injection SQL peut nous permettre de détourner un formulaire d'inscription (donc une insertion de données) afin d'obtenir des droits que nous n'aurions normalement pas dû avoir.

Comme d'habitude, voici le code PHP que nous utiliserons pour cet exercice.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php
    // code source de connexion.php
    $host = "localhost";
    $user_mysql = "root";    // nom d'utilisateur de l'utilisateur de MySQL 
    $password_mysql = "";    // mot de passe de l'utilisateur de MySQL
    $database = "zds_injections_sql";

    $db = mysqli_connect($host, $user_mysql, $password_mysql, $database);

    if(!$db)
    {
        echo "Echec de la connexion\n";
        exit();
    }

    mysqli_set_charset($db, "utf8");
?>

<!DOCTYPE>
<html>
    <head>
        <title></title>
        <style>
            input
            {
                display: block;
            }
        </style>
    </head>
    <body>
        <h1>Inscription</h1>

        <?php
        if(!empty($_GET['username']) && !empty($_GET['password']))
        {
            $username = $_GET['username'];
            $password = $_GET['password'];

            $query = "SELECT username FROM users WHERE username = '".$username."'";
            $rs = mysqli_query($db, $query);

            if(mysqli_num_rows($rs) >= 1)
            {
                echo "Ce pseudo est déjà utilisé.\n";
            }
            else
            {
                mysqli_query($db, "INSERT INTO users (username, password, rank) VALUES ('".$username."', '".$password."', 2)");
            }

            mysqli_free_result($rs);
            mysqli_close($db);
        }
        ?>

        <form action="inscription.php" method="GET">
            <b>Pseudo :</b> <input type="text" name="username"/>
            <b>Mot de passe :</b> <input type="text" name="password" />
            <input type="submit" value="S'inscrire" />
        </form>
    </body>
</html>

Contenu de connexion.php

Exploitation

http://localhost/zds/injections_sql/inscription.php

Il s'agit d'un simple formulaire d'inscription. L'utilisateur entre un nom d'utilisateur et un mot de passe. Si ce pseudo n'est pas déjà utilisé, alors on l'inscrit dans la base de données. Vous pouvez remarquez qu'il y a, à la fin de la requête d'insertion, la valeur 2. Il s'agit en fait ici du rang de l'utilisateur : 1 pour les administrateurs, 2 pour les simples utilisateurs.

Voici les utilisateurs actuellement présents dans la table users.

Comme vous pouvez le constater, seul l'utilisateur admin a le rang d'administrateur.

Testons ce formulaire de façon « normale ».

http://localhost/zds/injections_sql/inscription.php?username=test&password=test

Voyons ensuite si notre utilisateur a bien été ajouté.

user2

C'est bien le cas, et il a effectivement le rang 2 (simple utilisateur).

Maintenant, nous allons de nouveau nous inscrire mais, cette fois-ci, avec le rang 1 (c'est-à-dire le rang d'administrateur). C'est ici que notre injection SQL va rentrer en jeu. ;-)

Si vous ne l'avez pas remarqué, le nom d'utilisateur n'est pas échappé. Cette erreur va nous permettre de fermer la chaîne et de continuer la requête. Nous allons donc introduire un nom d'utilisateur, fermer la chaîne, introduire le mot de passe et pour finir – et c'est surtout cette partie là qui nous intéresse – le rang. Il ne faudra pas non plus oublier notre petit commentaire afin d'ignorer ce qui resterait de la requête initiale.

http://localhost/zds/injections_sql/inscription.php?username=john', 'doe', 1)%23&password=test

Pour rappel, ce qui suit le # (encodé en %23 dans l'url) est ignoré.

Vous obtiendrez probablement des erreurs : c'est normal, c'est la requête qui vérifie si le pseudo est présent dans la base de données ou non qui plante à cause de notre injection.

Voici la requête d'insertion complète.

1
INSERT INTO users (username, password, rank) VALUES ('john', 'doe', 1)#', 'test', 2)

Regardons notre liste des utilisateurs.

Gagné ! ^^


Comme nous venons de le voir, les injections SQL ne se limitent pas à la sélection de données. Les autres opérations (insertion, modification, suppression) sont tout aussi vulnérables.