recherche

Maison  >  Questions et réponses  >  le corps du texte

Comment prévenir les attaques par injection SQL en PHP ?

<p>Si une entrée utilisateur est insérée dans une requête SQL sans modification, l'application devient vulnérable aux attaques par injection SQL, comme dans l'exemple suivant : </p> <pre class="lang-php Prettyprint-override"><code>$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ("$unsafe_variable')"); </code></pre> <p>Cela est dû au fait que l'utilisateur peut saisir quelque chose comme <code>value'); DROP TABLE table;--</code> et la requête deviendra : </p> <pre class="brush:php;toolbar:false;">INSERT INTO `table` (`column`) VALUES('value');--')</pre> <p>Comment puis-je empêcher que cela se produise ? </p>
P粉939473759P粉939473759456 Il y a quelques jours540

répondre à tous(2)je répondrai

  • P粉891237912

    P粉8912379122023-08-23 00:21:40

    Pour utiliser des requêtes paramétrées, vous devez utiliser Mysqli ou PDO. Pour réécrire votre exemple en utilisant mysqli, nous aurions besoin d'un code similaire au suivant.

    <?php
    mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
    $mysqli = new mysqli("服务器", "用户名", "密码", "数据库名称");
    
    $variable = $_POST["user-input"];
    $stmt = $mysqli->prepare("INSERT INTO 表名 (列名) VALUES (?)");
    // "s"表示数据库期望一个字符串
    $stmt->bind_param("s", $variable);
    $stmt->execute();

    Les fonctions clés que vous voudrez peut-être lire sont mysqli::prepare.

    De plus, comme d'autres l'ont suggéré, vous trouverez peut-être plus utile/plus facile d'utiliser une couche d'abstraction de niveau supérieur comme PDO.

    Veuillez noter que la situation que vous avez mentionnée est assez simple, des situations plus complexes peuvent nécessiter des approches plus sophistiquées. Surtout :

    • Si vous souhaitez modifier la structure de votre SQL en fonction des entrées de l'utilisateur, les requêtes paramétrées ne vous aideront pas et l'échappement requis n'est pas inclus dans mysql_real_escape_string. Dans ce cas, il est préférable de transmettre les entrées de l'utilisateur via une liste blanche pour garantir que seules les valeurs « sûres » sont autorisées.

    répondre
    0
  • P粉771233336

    P粉7712333362023-08-23 00:20:03

    Quelle que soit la base de données que vous utilisez, la bonnefaçon d'éviter les attaques par injection SQL est de séparer les données du SQL afin que les données restent sous la forme de données qui ne seront jamais interprétées comme des commandes par l'analyseur SQL. . Il est possible de créer des instructions SQL avec des parties de données correctement formatées, mais si vous ne comprenez pas complètement les détails, vous devez toujours utiliser des instructions préparées et des requêtes paramétrées. Il s'agit d'instructions SQL envoyées séparément de tout paramètre et analysées par le serveur de base de données. De cette façon, les attaquants ne peuvent pas injecter du SQL malveillant. Il existe essentiellement deux manières d'y parvenir :

    Utilisation de

      PDO
    1. (fonctionne avec n'importe quel pilote de base de données pris en charge) :

      $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
      $stmt->execute([ 'name' => $name ]);
      
      foreach ($stmt as $row) {
          // 对$row进行操作
      }
      
      Utilisez

    2. MySQLi
    3. (pour MySQL) : Depuis PHP 8.2+, nous pouvons utiliser les méthodes
      pour préparer, lier des paramètres et exécuter des instructions SQL :

      $result = $db->execute_query('SELECT * FROM employees WHERE name = ?', [$name]);
       while ($row = $result->fetch_assoc()) {
           // 对$row进行操作
       }
      
      execute_query()Avant PHP8.1 :

       $stmt = $db->prepare('SELECT * FROM employees WHERE name = ?');
       $stmt->bind_param('s', $name); // 's'指定变量类型 => 'string'
       $stmt->execute();
       $result = $stmt->get_result();
       while ($row = $result->fetch_assoc()) {
           // 对$row进行操作
       }
      

      Si vous vous connectez à une base de données autre que MySQL, vous pouvez vous référer à la deuxième option spécifique au pilote (pour PostgreSQL par exemple, vous pouvez utiliser

      ). L’AOP est une option universelle.

    Établissez correctement la connexionpg_prepare()pg_execute()

    AOP

    Veuillez noter que lorsque vous utilisez

    PDO

    pour accéder à une base de données MySQL, les véritables

    instructions préparées ne sont pas utilisées par défaut

    . Pour résoudre ce problème, vous devez désactiver la simulation des instructions préparées. Voici un exemple de création d'une connexion à l'aide de PDO :

    $dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8mb4', 'user', 'password');
    
    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
    Dans l'exemple ci-dessus, le mode erreur n'est pas strictement obligatoire, mais il est recommandé de l'ajouter . De cette façon, PDO vous informera de toutes les erreurs MySQL en lançant

    .

    Ce doit PDOExceptionêtre, cependant, c'est la première ligne

    , qui indique à PDO de désactiver les instructions préparées simulées et d'utiliser des instructions préparées

    réelles . Cela garantit que les instructions et les valeurs ne sont pas analysées par PHP avant d'être envoyées au serveur MySQL (les attaquants potentiels ne peuvent donc pas injecter du SQL malveillant). setAttribute()Bien que vous puissiez le définir dans les options du constructeur, il est important de noter que les "anciennes" versions de PHP (antérieures à 5.3.6) ignorent silencieusement le paramètre charset

    dans le DSN.

    charsetMysqli Pour mysqli, nous devons suivre la même routine :

    mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // 错误报告
    $dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test');
    $dbConnection->set_charset('utf8mb4'); // 字符集
    

    Explication

    Lorsque vous réussissez
    , l'instruction préparée sera combinée avec les valeurs des paramètres spécifiés.

    La chose importante ici est que la valeur du paramètre est combinée avec l'instruction compilée, pas avec la chaîne SQL. L'injection SQL fonctionne en incitant un script à inclure une chaîne malveillante lors de la création du code SQL à envoyer à la base de données. Par conséquent, en envoyant le SQL réel séparément des paramètres, vous limitez le risque de résultats inattendus.

    Tous les paramètres envoyés à l'aide d'instructions préparées seront traités comme des chaînes (bien que le moteur de base de données puisse effectuer certaines optimisations sur les paramètres, de sorte que les paramètres peuvent finir par être des nombres). Dans l'exemple ci-dessus, si $name变量包含'Sarah'; DELETE FROM employees,结果将仅是搜索字符串"'Sarah'; DELETE FROM employees" vous n'obtiendriez pas une table vide.

    Un autre avantage de l'utilisation d'instructions préparées est que si la même instruction est exécutée plusieurs fois au cours de la même session, elle ne sera analysée et compilée qu'une seule fois, améliorant ainsi une certaine vitesse.

    Oh, puisque vous avez demandé comment opérer sur les inserts, voici un exemple (en utilisant PDO) :

    $preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
    
    $preparedStatement->execute([ 'column' => $unsafeValue ]);
    

    Les instructions préparées sont-elles adaptées aux requêtes dynamiques ?

    Bien que vous puissiez toujours utiliser des instructions préparées pour les paramètres de requête, la structure de la requête dynamique elle-même ne peut pas être paramétrée et certaines fonctions de requête ne peuvent pas être paramétrées.

    Pour ces scénarios spécifiques, la meilleure pratique consiste à utiliser un filtre de liste blanche pour limiter les valeurs possibles.

    // 值白名单
    // $dir只能是'DESC',否则将为'ASC'
    if (empty($dir) || $dir !== 'DESC') {
       $dir = 'ASC';
    }

    répondre
    0
  • Annulerrépondre