recherche

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

Comment empêcher l’injection SQL en PHP ?

<p>Si une entrée utilisateur est insérée dans une requête SQL sans modification, l'application est vulnérable à l'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>, la requête devient : </p> <pre class="brush:php;toolbar:false;">INSERT INTO `table` (`column`) VALUES('value');--')</pre> <p>Que peut-on faire pour éviter que cela ne se produise ? </p>
P粉128563140P粉128563140481 Il y a quelques jours648

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

  • P粉238355860

    P粉2383558602023-09-01 15:03:48

    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 des éléments suivants.

    <?php
    mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
    $mysqli = new mysqli("server", "username", "password", "database_name");
    
    $variable = $_POST["user-input"];
    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
    // "s" means the database expects a string
    $stmt->bind_param("s", $variable);
    $stmt->execute();

    La fonction clé que vous devez lire est mysqli::prepare.

    De plus, comme d'autres l'ont suggéré, vous pourriez utiliser quelque chose comme PDO< 之类的东西来提升抽象层很有用/更容易/a>.

    Veuillez noter que le cas sur lequel vous posez la question est assez simple, les cas plus complexes peuvent nécessiter des méthodes plus sophistiquées. Surtout :

    • Si vous souhaitez modifier la structure SQL en fonction des entrées de l'utilisateur, les requêtes paramétrées ne vous aideront pas et mysql_real_escape_stringmysql_real_escape_string ne contient pas l'échappement requis. Dans ce cas, vous feriez mieux 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粉283559033

    P粉2835590332023-09-01 11:53:19

    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 soient toujours des données et >ne soient jamais interprétées comme des commandes par l'analyseur SQL. Il est possible de créer des instructions SQL en utilisant des parties de données correctement formatées, mais si vous comprenez parfaitement les détails, vous devez toujours utiliser des instructions préparées et des requêtes paramétrées. est une instruction SQL envoyée et analysée par le serveur de base de données séparément de tout paramètre. De cette façon, il est impossible pour un attaquant d’injecter du SQL malveillant.

    Vous avez essentiellement deux options pour y parvenir :

    1. Utilisez

      PDO (pour tout 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) {
          // Do something with $row
      }
      

    2. Utilisez

      MySQLi (pour MySQL) :
      À partir de PHP 8.2+, nous pouvons utiliser execute_query()< code> pour préparer, lier des paramètres et exécuter des instructions SQL en une seule méthode :

      $result = $db->execute_query('SELECT * FROM employees WHERE name = ?', [$name]);
       while ($row = $result->fetch_assoc()) {
           // Do something with $row
       }
      

      Jusqu'à PHP8.1 :

       $stmt = $db->prepare('SELECT * FROM employees WHERE name = ?');
       $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
       $stmt->execute();
       $result = $stmt->get_result();
       while ($row = $result->fetch_assoc()) {
           // Do something with $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 (par exemple,

    pg_prepare()pg_prepare() et pg_execute()< /code> pour PostgreSQL). L’AOP est une option universelle.


    Établissez correctement la connexion

    AOP

    Veuillez noter que lorsque vous utilisez PDO pour accéder à une base de données MySQL, les réelles instructions préparéesne sont pas utilisées par défaut. Pour résoudre ce problème, vous devez désactiver la simulation des instructions préparées. Un exemple de création d'une connexion à l'aide de PDO est :

    $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 nécessaire, mais il est recommandé de l'ajouter . De cette façon, PDO vous informera de toutes les erreurs MySQL en lançant PDOExceptionPDOException

    .

    Cependant, forcer setAttribute() est la première ligne setAttribute(), qui indique à PDO de désactiver les instructions préparées simulées et d'utiliser les instructions préparées

    real

    . Cela garantit que les instructions et les valeurs ne sont pas analysées par PHP avant d'être envoyées au serveur MySQL (ne donnant aucune chance à un attaquant potentiel d'injecter du SQL malveillant). 字符集 Bien que vous puissiez définir le charset

    dans les options du constructeur, il est important de noter que les versions "anciennes" de PHP (antérieures à 5.3.6)

    ignorent silencieusement le paramètre charset

    dans le DSN.

    Mysqli🎜 🎜Pour mysqli nous devons suivre la même routine : 🎜
    mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting
    $dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test');
    $dbConnection->set_charset('utf8mb4'); // charset
    

    Instructions

    Lorsque vous réussissez prepare的SQL语句由数据库服务器解析和编译。通过指定参数(? 或命名参数,如上例中的 :name),您可以告诉数据库引擎您要过滤的位置。然后,当您调用 execute, l'instruction préparée sera combinée avec les valeurs des paramètres que vous spécifiez.

    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 à contenir une chaîne malveillante lorsqu'il crée le SQL à envoyer à la base de données. Ainsi, en envoyant le SQL réel séparément des paramètres, vous limitez le risque de vous retrouver avec quelque chose d'inattendu.

    Tous les paramètres que vous envoyez lors de l'utilisation 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, les paramètres peuvent donc bien sûr également être traités comme des nombres). Dans l’exemple ci-dessus, si $name 变量包含 'Sarah'; DELETE FROMEmployees 结果只是搜索字符串 "'Sarah'; DELETE FROMEmployees", et vous ne vous retrouvez pas avec une table vide.

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

    Oh, puisque vous avez demandé comment faire l'insertion, voici un exemple (en utilisant PDO) :

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

    Les instructions préparées peuvent-elles être utilisées pour les requêtes dynamiques ?

    Bien que vous puissiez toujours utiliser des instructions préparées avec des 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 non plus être paramétrées.

    Pour ces scénarios spécifiques, la meilleure chose à faire est d'utiliser un filtre de liste blanche pour limiter les valeurs possibles.

    // Value whitelist
    // $dir can only be 'DESC', otherwise it will be 'ASC'
    if (empty($dir) || $dir !== 'DESC') {
       $dir = 'ASC';
    }

    répondre
    0
  • Annulerrépondre