PHP Empêcher l'injection SQL avec des requêtes paramétrées


Exemple

L'injection SQL est une sorte d'attaque qui permet à un utilisateur malveillant de modifier la requête SQL en y ajoutant des commandes indésirables. Par exemple, le code suivant est vulnérable :

// Do not use this vulnerable code!
$sql = 'SELECT name, email, user_level FROM users WHERE userID = ' . $_GET['user'];
$conn->query($sql);

Cela permet à tout utilisateur de ce script de modifier fondamentalement notre base de données à volonté. Par exemple, considérez la chaîne de requête suivante:

page.php?user=0;%20TRUNCATE%20TABLE%20users;

Cela rend notre exemple de requête comme ceci

SELECT name, email, user_level FROM users WHERE userID = 0; TRUNCATE TABLE users;

Bien que ce soit un exemple extrême (la plupart des attaques par injection SQL ne visent pas à supprimer des données, la plupart des fonctions d’exécution de requêtes PHP ne prennent pas en charge les requêtes multiples). la requête. Malheureusement, de telles attaques sont très courantes et sont très efficaces grâce aux codeurs qui ne prennent pas les précautions nécessaires pour protéger leurs données.

Pour empêcher l'injection SQL, les instructions préparées sont la solution recommandée. Au lieu de concaténer les données utilisateur directement dans la requête, un espace réservé est utilisé à la place. Les données sont ensuite envoyées séparément, ce qui signifie que le moteur SQL n'a aucune chance de confondre les données utilisateur avec un ensemble d'instructions.

Bien que le sujet ici soit PDO, veuillez noter que l'extension PHP MySQLi prend également en charge les instructions préparées

PDO prend en charge deux types d'espaces réservés (les espaces réservés ne peuvent pas être utilisés pour les noms de colonne ou de table, uniquement les valeurs):

  1. Espaces réservés nommés. A deux points ( : ), suivi par un nom distinct (par ex. :user L' :user )

    // using named placeholders
    $sql = 'SELECT name, email, user_level FROM users WHERE userID = :user';
    $prep = $conn->prepare($sql);
    $prep->execute(['user' => $_GET['user']]); // associative array
    $result = $prep->fetchAll();
    
  2. Caractères de position SQL traditionnels représentés sous la forme ? :

    // using question-mark placeholders
    $sql = 'SELECT name, user_level FROM users WHERE userID = ? AND user_level = ?';
    $prep = $conn->prepare($sql);
    $prep->execute([$_GET['user'], $_GET['user_level']]); // indexed array
    $result = $prep->fetchAll();
    

Si vous avez besoin de modifier dynamiquement les noms de tables ou de colonnes, sachez que cela présente des risques pour votre sécurité et une mauvaise pratique. Cependant, cela peut être fait par concaténation de chaînes. Un moyen d'améliorer la sécurité de ces requêtes consiste à définir une table de valeurs autorisées et à comparer la valeur que vous souhaitez concaténer à cette table.

Sachez qu'il est important de définir le charset de connexion uniquement via DSN, sinon votre application pourrait être sujette à une vulnérabilité obscure si un codage impair est utilisé. Pour les versions PDO antérieures à 5.3.6, le paramètre charset via DSN n'est pas disponible et la seule option consiste à définir l'attribut PDO::ATTR_EMULATE_PREPARES sur false à la connexion juste après sa création.

$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Cela amène PDO à utiliser les instructions préparées natives du SGBD sous-jacent au lieu de simplement les émuler.

Cependant, sachez que PDO se rabat silencieusement sur des instructions que MySQL ne peut pas préparer en mode natif: celles qui le sont sont listées dans le manuel ( source ).