mysql protection - Comment puis-je empêcher l'injection SQL en PHP?




function avoid (24)

MYSQLi est MYSQLi d’empêcher les injections SQL d’ utiliser PDO et MYSQLi , mais si vous souhaitez réellement utiliser des fonctions et des requêtes MySQL, il est préférable d’utiliser

mysql_real_escape_string()

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

Il existe plus de possibilités pour empêcher cela: comme identifier - si l'entrée est une chaîne, un nombre, un caractère ou un tableau, il y a tellement de fonctions intégrées pour le détecter. En outre, il serait préférable d’utiliser ces fonctions pour vérifier les données d’entrée.

is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

Et il est tellement préférable d'utiliser ces fonctions pour vérifier les données d'entrée mysql_real_escape_string.

Si l'entrée utilisateur est insérée sans modification dans une requête SQL, l'application devient vulnérable à l' injection SQL , comme dans l'exemple suivant:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

C'est parce que l'utilisateur peut entrer quelque chose comme value'); DROP TABLE table;-- value'); DROP TABLE table;-- et la requête devient:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Que peut-on faire pour empêcher cela?


IMPORTANT

Le meilleur moyen d'empêcher l'injection SQL consiste à utiliser les instructions préparées au lieu d'échapper , comme le montre la réponse acceptée .

Il existe des bibliothèques telles que Aura.Sql et EasyDB qui permettent aux développeurs d’utiliser plus facilement les instructions préparées. Pour en savoir plus sur les raisons pour lesquelles les instructions préparées arrivent mieux à arrêter l'injection SQL , reportez-vous à ce contournement de mysql_real_escape_string() et aux vulnérabilités Unicode SQL Injection récemment corrigées dans WordPress .

Prévention des injections - mysql_real_escape_string()

PHP a une fonction spécialement conçue pour empêcher ces attaques. Tout ce que vous avez à faire est d’utiliser la fonction mysql_real_escape_string .

mysql_real_escape_string prend une chaîne qui va être utilisée dans une requête MySQL et renvoie la même chaîne avec toutes les tentatives d'injection SQL échappées en toute sécurité. En gros, il remplacera les guillemets gênants (') qu'un utilisateur peut entrer avec un substitut compatible avec MySQL, un guillemet échappé \'.

NOTE: vous devez être connecté à la base de données pour utiliser cette fonction!

// Se connecter à MySQL

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Vous trouverez plus de détails dans MySQL - Prévention des injections SQL .


J'utilise trois méthodes différentes pour éviter que mon application Web ne soit vulnérable à l'injection SQL.

  1. L' utilisation de mysql_real_escape_string(), qui est une fonction prédéfinie dans PHP , et ce code ajouter un slash aux caractères suivants: \x00, \n, \r, \, ', "et \x1a. Transmettez les valeurs d'entrée en tant que paramètres afin de minimiser les risques d'injection SQL.
  2. Le moyen le plus avancé consiste à utiliser des PDO.

J'espère que cela t'aidera.

Considérons la requête suivante:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string () ne protégera pas ici. Si vous utilisez des guillemets simples ('') autour de vos variables dans votre requête, vous êtes protégé contre cela. Voici une solution ci-dessous pour cela:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

Cette question a de bonnes réponses à ce sujet.

Je suggère, l'utilisation de PDO est la meilleure option.

Modifier:

mysql_real_escape_string()est obsolète depuis PHP 5.5.0. Utilisez soit mysqli ou PDO.

Une alternative à mysql_real_escape_string () est

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Exemple:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");

Pour ceux qui ne savent pas utiliser PDO (à partir des mysql_fonctions), j'ai créé un wrapper PDO très simple qui consiste en un fichier unique. Il existe pour montrer à quel point il est facile de faire tout ce que les applications doivent faire. Fonctionne avec PostgreSQL, MySQL et SQLite.

Fondamentalement, le lire PDO pour voir comment mettre les fonctions AOP à utiliser dans la vie réelle pour le rendre simple pour stocker et récupérer des valeurs dans le format que vous voulez.

Je veux une seule colonne

$count = DB::column('SELECT COUNT(*) FROM `user`);

Je veux un tableau (clé => valeur) résultats (ie pour faire une selectbox)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

Je veux un résultat sur une seule ligne

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

Je veux un tableau de résultats

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

Requête paramétrée ET validation des entrées est la voie à suivre. Il existe de nombreux scénarios dans lesquels une injection SQL peut avoir lieu, même si mysql_real_escape_string() a été utilisé.

Ces exemples sont vulnérables à l'injection SQL:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

ou

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

Dans les deux cas, vous ne pouvez pas utiliser ' pour protéger l'encapsulation.

Source : l' injection SQL inattendue (lorsque l'évacuation n'est pas suffisante)


Je privilégie les procédures stockées ( MySQL prend en charge les procédures stockées depuis la version 5.0 ) du point de vue de la sécurité - les avantages sont -

  1. La plupart des bases de données (y compris MySQL ) permettent de limiter l'accès des utilisateurs à l'exécution de procédures stockées. Le contrôle d'accès de sécurité à granularité fine est utile pour empêcher les attaques d'escalade de privilèges. Cela empêche les applications compromises d'exécuter directement du SQL sur la base de données.
  2. Ils font abstraction de la requête SQL brute de l’application afin que moins d’informations sur la structure de la base de données soient disponibles pour l’application. Cela rend plus difficile la compréhension de la structure sous-jacente de la base de données et la conception d’attaques appropriées.
  3. Ils n'acceptent que les paramètres, les avantages des requêtes paramétrées sont donc là. Bien sûr - IMO, vous devez encore purifier votre saisie - surtout si vous utilisez du SQL dynamique dans la procédure stockée.

Les inconvénients sont -

  1. Ils (procédures stockées) sont difficiles à maintenir et ont tendance à se multiplier très rapidement. Cela rend leur gestion un problème.
  2. Ils ne conviennent pas très bien aux requêtes dynamiques. S'ils sont conçus pour accepter le code dynamique en tant que paramètres, de nombreux avantages sont annulés.

En ce qui concerne de nombreuses réponses utiles, j'espère ajouter quelques valeurs à ce fil. L’injection SQL est une attaque qui peut être lancée via des entrées utilisateur (entrées remplies par l’utilisateur, puis utilisées dans des requêtes). être une mauvaise personne qui tente d'obtenir des informations secrètes (en contournant le contrôle d'accès) qui affectent les trois principes de sécurité (confidentialité, intégrité, disponibilité).

Il s’agit maintenant de prévenir les menaces à la sécurité telles que les attaques par injection SQL, la question posée (comment empêcher une attaque par injection SQL avec PHP), d’être plus réaliste, le filtrage des données ou la suppression des données d’entrée est le cas lorsqu’on utilise des données utilisateur requête, en utilisant PHP ou tout autre langage de programmation n’est pas le cas, ou comme le recommandent de plus en plus d’utiliser des technologies modernes telles que les instructions préparées ou tout autre outil prenant actuellement en charge la prévention de l’injection SQL, estimez-vous que ces outils ne sont plus disponibles? Comment sécurisez-vous votre application?

Mon approche vis-à-vis de l'injection SQL consiste à: effacer les données saisies par l'utilisateur avant de les envoyer à la base de données (avant de les utiliser dans une requête).

Filtrage des données pour (Conversion de données non sécurisées en données sécurisées) Considérez que PDO et MySQLi ne sont pas disponibles, comment pouvez-vous sécuriser votre application? Me forcez-vous à les utiliser? Qu'en est-il des langues autres que PHP? Je préfère fournir des idées générales car cela peut être utilisé pour une frontière plus large et pas seulement pour une langue spécifique.

  1. Utilisateur SQL (privilège utilisateur limité): les opérations SQL les plus courantes sont (SELECT, UPDATE, INSERT), alors pourquoi donner des privilèges UPDATE à un utilisateur qui n'en a pas besoin? Par exemple , les pages de connexion et de recherche utilisent uniquement SELECT, pourquoi utiliser des utilisateurs de base de données dans ces pages avec des privilèges élevés? RÈGLE: ne créez pas un utilisateur de base de données pour tous les privilèges, pour toutes les opérations SQL, vous pouvez créer votre schéma tel que (deluser, selectuser, updateuser) en tant que noms d'utilisateur faciles à utiliser.

voir le principe du moindre privilège

  1. Filtrage des données: avant de créer une requête, les entrées utilisateur doivent être validées et filtrées. Pour les programmeurs, il est important de définir des propriétés pour chaque variable saisie par l'utilisateur: type de données, modèle de données et longueur des données . un champ qui est un nombre compris entre (x et y) doit être exactement validé en utilisant la règle exacte, pour un champ qui est une chaîne (text): motif est le cas, par exemple, nom d'utilisateur ne doit contenir que quelques caractères, disons [a- zA-Z0-9_-.] la longueur varie entre (x et n) où x et n (nombres entiers, x <= n). Règle: la création de filtres exacts et de règles de validation est la meilleure pratique pour moi.

  2. Utilisez d’autres outils: ici, je conviens également avec vous que l’instruction préparée (requête paramétrée) et les procédures stockées, les inconvénients de cette méthode nécessitent des compétences avancées qui n’existent pas pour la plupart des utilisateurs; et les données qui sont utilisées à l'intérieur, les deux approches peuvent être utilisées même avec des données non sécurisées, car les données saisies par l'utilisateur ici n'ajoutent rien à la requête d'origine telle que (any ou x = x). Pour plus d’informations, veuillez consulter le Cheat Sheet de prévention d’injection SQL OWASP .

Maintenant, si vous êtes un utilisateur expérimenté, commencez à utiliser cette défense à votre guise, mais pour les débutants, s'ils ne peuvent pas rapidement mettre en oeuvre une procédure stockée et préparer l'instruction, il est préférable de filtrer autant que possible les données d'entrée.

Enfin, considérons que l’utilisateur envoie ce texte ci-dessous au lieu de saisir son nom d’utilisateur:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

Cette entrée peut être vérifiée rapidement sans instruction préparée ni procédures stockées, mais pour être sûr, leur utilisation commence après le filtrage et la validation des données utilisateur.

Le dernier point concerne la détection de comportements inattendus nécessitant plus d’efforts et de complexité; ce n'est pas recommandé pour les applications web normales. Le comportement inattendu dans l'entrée d'utilisateur ci-dessus est SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA, racine une fois ces mots détectés, vous pouvez éviter l'entrée.

UPDATE1:

Un utilisateur a commenté que ce post était inutile, OK! Voici ce que OWASP.ORG fourni:

Principales défenses:

Option n ° 1: Utilisation des instructions préparées (requêtes paramétrées)
Option n ° 2: Utilisation des procédures stockées
Option n ° 3: Échapper à toutes les entrées fournies par l'utilisateur

Défenses supplémentaires:

Appliquer également: Privilège
minimum

Comme vous le savez peut-être, la revendication d'un article doit être étayée par un argument valide, au moins une référence! Sinon, c'est considéré comme une attaque et une mauvaise réclamation!

Update2:

Extrait du manuel PHP, PHP: Instructions préparées - Manuel :

Échappement et injection SQL

Les variables liées seront automatiquement échappées par le serveur. Le serveur insère leurs valeurs échappées aux emplacements appropriés dans le modèle d’instruction avant exécution. Un indice doit être fourni au serveur pour le type de variable liée afin de créer une conversion appropriée. Voir la fonction mysqli_stmt_bind_param () pour plus d'informations.

L'échappement automatique de valeurs sur le serveur est parfois considéré comme une fonction de sécurité empêchant l'injection SQL. Le même degré de sécurité peut être atteint avec des instructions non préparées si les valeurs d'entrée sont correctement échappées.

Update3:

J'ai créé des scénarios de test pour savoir comment PDO et MySQLi envoient la requête au serveur MySQL lors de l'utilisation d'une instruction préparée:

AOP:

$user = "''1''"; //Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

Journal de requêtes:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

Journal de requêtes:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

Il est clair qu’une déclaration préparée échappe également aux données, rien d’autre.

Comme mentionné également dans la déclaration ci-dessus The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly, cela prouve donc que la validation des données telle qu’elle intval()est une bonne idée pour les valeurs entières avant d’envoyer une requête, de plus, empêcher les données d’utilisateur malveillant avant d’envoyer la requête est une approche correcte et valide .

Veuillez consulter cette question pour plus de détails: PDO envoie une requête brute à MySQL alors que Mysqli envoie une requête préparée, les deux résultats sont identiques.

Références:

  1. Aide-mémoire d'injection SQL
  2. Injection SQL
  3. Sécurité de l'information
  4. Principes de sécurité
  5. La validation des données

Quelques instructions pour éviter les caractères spéciaux dans les instructions SQL.

N'utilisez pas MySQL , cette extension est déconseillée, utilisez MySQLi ou PDO .

MySQLi

Pour échapper manuellement des caractères spéciaux dans une chaîne, vous pouvez utiliser la fonction mysqli_real_escape_string . La fonction ne fonctionnera pas correctement si le bon jeu de caractères n'est pas défini avec mysqli_set_charset .

Exemple:

$mysqli = new mysqli( 'host', 'user', 'password', 'database' );
$mysqli->set_charset( 'charset');

$string = $mysqli->real_escape_string( $string );
$mysqli->query( "INSERT INTO table (column) VALUES ('$string')" );

Pour l'échappement automatique de valeurs avec des instructions préparées, utilisez mysqli_prepare et mysqli_stmt_bind_param où les types des variables de liaison correspondantes doivent être fournis pour une conversion appropriée:

Exemple:

$stmt = $mysqli->prepare( "INSERT INTO table ( column1, column2 ) VALUES (?,?)" );

$stmt->bind_param( "is", $integer, $string );

$stmt->execute();

Peu importe que vous utilisiez des instructions préparées ou mysqli_real_escape_string, vous devez toujours connaître le type de données d'entrée avec lequel vous travaillez.

Donc, si vous utilisez une instruction préparée, vous devez spécifier les types de variables pour la fonction mysqli_stmt_bind_param.

Et mysqli_real_escape_string est utilisé, comme le nom l'indique, pour échapper des caractères spéciaux dans une chaîne, afin de ne pas sécuriser les entiers. Le but de cette fonction est d’empêcher la rupture des chaînes dans les instructions SQL et les dommages qu’elle pourrait causer à la base de données. mysqli_real_escape_string est une fonction utile lorsqu'elle est utilisée correctement, en particulier lorsqu'elle est associée à sprintf.

Exemple:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

** Avertissement: l'approche décrite dans cette réponse ne s'applique qu'à des scénarios très spécifiques et n'est pas sécurisée, car les attaques par injection SQL ne reposent pas uniquement sur la possibilité d'injecter X=Y. **

Si les attaquants essaient de pirater le formulaire via la $_GETvariable PHP ou avec la chaîne de requête de l'URL, vous pourrez les intercepter s'ils ne sont pas sécurisés.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

Parce que 1=1, 2=2, 1=2, 2=1, 1+1=2, etc ... sont les questions communes à une base de données SQL d'un attaquant. Peut-être aussi est-il utilisé par de nombreuses applications de piratage.

Mais vous devez faire attention à ne pas réécrire une requête sécurisée de votre site. Le code ci-dessus vous donne un conseil, pour réécrire ou rediriger (cela dépend de vous) cette chaîne de requête dynamique spécifique au piratage dans une page qui stockera l' adresse IP de l'attaquant , ou MÊME LEURS COOKIES, son historique, son navigateur ou tout autre élément sensible. informations, afin que vous puissiez les traiter plus tard en interdisant leur compte ou en contactant les autorités.


Il y a tellement de réponses pour PHP et MySQL , mais voici du code pour PHP et Oracle permettant d'empêcher l'injection SQL ainsi que l'utilisation régulière des pilotes oci8:

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);

Comme vous pouvez le constater, les gens suggèrent d’utiliser tout au plus les déclarations préparées. Ce n'est pas faux, mais lorsque votre requête est exécutée une seule fois par processus, les performances sont légèrement pénalisées.

Je faisais face à ce problème, mais je pense l’avoir résolu de manière très sophistiquée - comme le font les pirates informatiques pour éviter d’utiliser des guillemets. Je l'ai utilisé conjointement avec des déclarations préparées émulées. Je l'utilise pour prévenir toutes sortes d'attaques d'injection SQL possibles.

Mon approche:

  • Si vous vous attendez à ce que l'entrée soit un entier, assurez-vous qu'il s'agit bien d'un entier. Dans un langage de type variable tel que PHP, c'est très important. Vous pouvez utiliser par exemple cette solution très simple mais puissante: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Si vous attendez quelque chose d'autre de integer hex . Si vous l'enseignez, vous échapperez parfaitement à toutes les entrées. En C / C ++, il existe une fonction appelée mysql_hex_string() . En PHP, vous pouvez utiliser bin2hex() .

    Ne vous inquiétez pas, la longueur originale de la chaîne échappée sera 2x, car même si vous utilisez mysql_real_escape_string , PHP doit allouer la même capacité ((2*input_length)+1) , ce qui est la même chose.

  • Cette méthode hexadécimale est souvent utilisée lorsque vous transférez des données binaires, mais je ne vois pas pourquoi ne pas l’utiliser sur toutes les données pour empêcher les attaques par injection SQL. Notez que vous devez préfixer les données avec 0x ou utiliser à la place la fonction MySQL UNHEX .

Ainsi, par exemple, la requête:

SELECT password FROM users WHERE name = 'root'

Va devenir:

SELECT password FROM users WHERE name = 0x726f6f74

ou

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex est l'évasion parfaite. Pas moyen d'injecter.

Différence entre la fonction UNHEX et le préfixe 0x

Il y a eu des discussions dans les commentaires, je veux donc que tout soit clair. Ces deux approches sont très similaires, mais elles sont un peu différentes à certains égards:

Le préfixe ** 0x ** ne peut être utilisé que pour les colonnes de données telles que char, varchar, text, block, binary, etc.
En outre, son utilisation est un peu compliquée si vous êtes sur le point d'insérer une chaîne vide. Vous devrez le remplacer entièrement par '' ou vous obtiendrez une erreur.

UNHEX () fonctionne sur n’importe quelle colonne; vous n'avez pas à vous soucier de la chaîne vide.

Les méthodes Hex sont souvent utilisées comme attaques

Notez que cette méthode hex est souvent utilisée comme une attaque par injection SQL où les entiers sont comme des chaînes et échappés avec mysql_real_escape_string . Ensuite, vous pouvez éviter l'utilisation de guillemets.

Par exemple, si vous faites juste quelque chose comme ceci:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

une attaque peut vous injecter très facilement . Considérez le code injecté suivant renvoyé par votre script:

SELECT ... WHERE id = -1 union all sélectionne nom_table dans information_schema.tables

et maintenant, extrayez simplement la structure de la table:

SELECT ... WHERE id = -1 union all sélectionne nom_colonne à partir du fichier information_schema.column où nom_table = 0x61727469636c65

Et puis, sélectionnez simplement les données souhaitées. N'est-ce pas cool?

Mais si le codeur d'un site injectable l'hexageait, aucune injection ne serait possible car la requête ressemblerait à ceci: SELECT ... WHERE id = UNHEX('2d312075...3635')


Utilisez PDO et les requêtes préparées.

( $conn est un objet PDO )

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

À mon avis, le meilleur moyen d'empêcher généralement l'injection de SQL dans votre application PHP (ou n'importe quelle application Web, d'ailleurs) est de réfléchir à l'architecture de votre application. Si le seul moyen de se protéger contre l'injection SQL est de ne pas oublier d'utiliser une méthode ou une fonction spéciale permettant d'effectuer l'opération The Right Thing à chaque fois que vous parlez à la base de données. De cette façon, ce n'est qu'une question de temps jusqu'à ce que vous oubliez de formater correctement votre requête à un moment donné de votre code.

L'adoption du modèle MVC et d'un framework tel que CakePHP ou CodeIgniter est probablement la bonne voie à suivre: Des tâches courantes telles que la création de requêtes de base de données sécurisées ont été résolues et implémentées de manière centralisée dans de tels frameworks. Ils vous aident à organiser votre application Web de manière judicieuse et vous incitent davantage à charger et à enregistrer des objets qu'à créer en toute sécurité des requêtes SQL uniques.


Avertissement de sécurité : Cette réponse n'est pas conforme aux meilleures pratiques de sécurité. Échapper est inadéquat pour empêcher l'injection SQL , utilisez plutôt des instructions préparées . Utilisez la stratégie décrite ci-dessous à vos risques et périls. (En outre, mysql_real_escape_string() été supprimé de PHP 7.)

Vous pouvez faire quelque chose de fondamental comme ceci:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Cela ne résoudra pas tous les problèmes, mais c’est un très bon tremplin. J'ai omis des éléments évidents tels que la vérification de l'existence de la variable, son format (nombres, lettres, etc.).


Quoi que vous utilisiez, assurez-vous de vérifier que votre saisie n'a pas déjà été magic_quotes par magic_quotes ou une autre magic_quotes bien intentionnée, et si nécessaire, passez-la à l'aide de stripslashes ou de quoi que ce soit pour la désinfecter.


Si vous souhaitez tirer parti des moteurs de cache, tels que Redis ou Memcached , DALMP pourrait peut-être être un choix. Il utilise purement MySQLi . Vérifiez ceci: Couche d'abstraction de base de données DALMP pour MySQL avec PHP.

En outre, vous pouvez "préparer" vos arguments avant de préparer votre requête afin de pouvoir créer des requêtes dynamiques et disposer à la fin d'une requête d'instructions entièrement préparée. Couche d'abstraction de base de données DALMP pour MySQL avec PHP.


Il existe de nombreuses manières d'empêcher les injections SQL et autres piratages SQL. Vous pouvez facilement le trouver sur Internet (Recherche Google). Bien sûr, le PDO est l’une des bonnes solutions. Mais je voudrais vous suggérer une bonne prévention des liens de SQL Injection.

Qu'est-ce que l'injection SQL et comment prévenir

Manuel PHP pour l'injection SQL

Explication Microsoft de l'injection SQL et de la prévention en PHP

et d'autres comme Prévenir l'injection SQL avec MySQL et PHP

Maintenant, pourquoi avez-vous besoin d'empêcher votre requête d'injection SQL?

Je voudrais vous faire savoir: pourquoi essayons-nous d'empêcher l'injection SQL avec un court exemple ci-dessous:

Requête pour correspondance d'authentification de connexion:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

Maintenant, si quelqu'un (un pirate informatique) met

$_POST['email']= [email protected]' OR '1=1

et mot de passe quoi que ce soit ....

La requête ne sera analysée dans le système que jusqu'à:

$query="select * from users where email='[email protected]' OR '1=1';

L'autre partie sera éliminée. Alors, que va-t-il se passer? Un utilisateur non autorisé (pirate informatique) pourra se connecter en tant qu'administrateur sans avoir son mot de passe. Maintenant, il peut faire tout ce que peuvent faire les administrateurs et les courriers électroniques. Vous voyez, c'est très dangereux si l'injection SQL n'est pas empêchée.


Je vous recommande d'utiliser PDO (PHP Data Objects) pour exécuter des requêtes SQL paramétrées.

Cela protège non seulement contre l’injection SQL, mais aussi l’accélération des requêtes.

Et en utilisant PDO plutôt que les fonctions mysql_ , mysqli_ et pgsql_ , votre application est un peu plus abstraite de la base de données, dans les rares cas où vous devez changer de fournisseur de base de données.


Je pense que si quelqu'un veut utiliser PHP et MySQL ou un autre serveur de base de données:

  1. Pensez à l’apprentissage PDO (PHP Data Objects) - c’est une couche d’accès à une base de données offrant une méthode uniforme d’accès à plusieurs bases de données.
  2. Pensez à apprendre MySQLi
  3. Utilisez des fonctions PHP natives telles que: strip_tags , mysql_real_escape_string() ou si variable numérique, juste (int)$foo. En savoir plus sur le type de variables en PHP here . Si vous utilisez des bibliothèques telles que PDO ou MySQLi, utilisez toujours PDO::quote() et mysqli_real_escape_string() .

Exemples de bibliothèques:

---- AOP

----- Aucun espace réservé - mûr pour l'injection SQL! C'est mauvais

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- espaces réservés sans nom

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- Espaces réservés nommés

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

PS :

PDO gagne cette bataille avec facilité. Avec la prise en charge de douze pilotes de base de données différents et de paramètres nommés, nous pouvons ignorer la petite perte de performances et nous habituer à son API. Du point de vue de la sécurité, les deux sont sécurisés tant que le développeur les utilise comme ils sont censés être utilisés

Toutefois, bien que PDO et MySQLi soient assez rapides, MySQLi est extrêmement rapide dans les tests de performance: environ 2,5% pour les instructions non préparées et environ 6,5% pour les déclarations préparées.

Et testez chaque requête dans votre base de données. C'est un meilleur moyen de prévenir l'injection.


Utilisez des instructions préparées et des requêtes paramétrées. Il s'agit d'instructions SQL qui sont envoyées et analysées par le serveur de base de données indépendamment de tout paramètre. De cette manière, il est impossible à un attaquant d'injecter du code SQL malveillant.

Vous avez essentiellement deux options pour y parvenir:

  1. Utilisation de PDO (pour tout pilote de base de données pris en charge):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. Utilisation de MySQLi (pour MySQL):

    $stmt = $dbConnection->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 à une deuxième option spécifique au pilote (par exemple, pg_prepare() et pg_execute() pour PostgreSQL). Le PDO est l'option universelle.

Configurer correctement la connexion

Notez que lorsque vous utilisez PDO pour accéder à une base de données MySQL, les instructions réellement préparées ne sont pas utilisées par défaut . Pour résoudre ce problème, vous devez désactiver l'émulation 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=utf8', 'user', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Dans l'exemple ci-dessus, le mode d'erreur n'est pas strictement nécessaire, mais il est conseillé de l'ajouter . De cette façon, le script ne s’arrêtera pas avec une Fatal Error cas de problème. Et cela donne au développeur la possibilité d’ catch toute erreur (s) PDOException (s) en tant que PDOException .

Ce qui est obligatoire , cependant, est la première ligne setAttribute() , qui indique à PDO de désactiver les instructions préparées émulées et d'utiliser de véritables instructions préparées. Cela permet de s’assurer que la déclaration et les valeurs ne sont pas analysées par PHP avant de l’envoyer au serveur MySQL (ce qui permet à un attaquant éventuel d’injecter du code SQL malveillant).

Bien que vous puissiez définir le charset de charset dans les options du constructeur, il est important de noter que les versions "anciennes" de PHP (<5.3.6) ignoraient en silence le paramètre charset du DSN.

Explication

Ce qui se passe, c'est que l'instruction SQL que vous passez à prepare est analysée et compilée par le serveur de base de données. En spécifiant des paramètres (un ? Ou un paramètre nommé tel que :name dans l'exemple ci-dessus), vous indiquez au moteur de base de données sur lequel vous souhaitez filtrer. Ensuite, lorsque vous appelez execute , l'instruction préparée est combinée aux valeurs de paramètre que vous spécifiez.

La chose importante ici est que les valeurs de paramètre sont combinées avec l'instruction compilée, pas une chaîne SQL. L'injection SQL fonctionne en incitant le script à inclure des chaînes malveillantes lorsqu'il crée du code SQL à envoyer à la base de données. Donc, en envoyant le code SQL séparément des paramètres, vous limitez le risque de vous retrouver avec quelque chose que vous ne souhaitiez pas. Tous les paramètres que vous envoyez en utilisant une instruction préparée seront simplement traités comme des chaînes (bien que le moteur de base de données puisse effectuer certaines optimisations, les paramètres peuvent également se transformer en nombres, bien sûr). Dans l'exemple ci-dessus, si la variable $name contient 'Sarah'; DELETE FROM employees 'Sarah'; DELETE FROM employees le résultat serait simplement une recherche de la chaîne "'Sarah'; DELETE FROM employees" et vous ne vous retrouverez pas avec une table vide .

L’utilisation d’instructions préparées présente un autre avantage: 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, ce qui vous donnera un gain de rapidité.

Oh, et puisque vous avez demandé comment faire pour une insertion, voici un exemple (en utilisant PDO):

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

$preparedStatement->execute(array('column' => $unsafeValue));

Des instructions préparées peuvent-elles être utilisées pour des 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 fonctionnalités de la requête ne peuvent pas être paramétrées.

Pour ces scénarios spécifiques, la meilleure chose à faire consiste à utiliser un filtre de liste blanche limitant les valeurs possibles.

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

Une bonne idée est d'utiliser un "mappeur relationnel-objet" comme Idiorm :

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

Cela vous évite non seulement des injections SQL mais aussi des erreurs de syntaxe! Prend également en charge les collections de modèles avec chaînage de méthodes pour filtrer ou appliquer des actions à plusieurs résultats à la fois et à plusieurs connexions.


Une méthode simple consisterait à utiliser un environnement PHP tel que CodeIgniter ou Laravel qui intègre des fonctionnalités telles que le filtrage et l'enregistrement actif, de sorte que vous n'ayez pas à vous soucier de ces nuances.


Avertissement: l'exemple de code de cette réponse (comme l'exemple de code de la question) utilise l'extension mysql de PHP, obsolète en PHP 5.5.0 et entièrement supprimée en PHP 7.0.0.

Si vous utilisez une version récente de PHP, l'option mysql_real_escape_string décrite ci-dessous ne sera plus disponible (bien que mysqli::escape_string soit un équivalent moderne). De nos jours, l'option mysql_real_escape_string n'aurait de sens que pour le code hérité d'une ancienne version de PHP.

Vous avez deux options: échapper les caractères spéciaux dans votre unsafe_variable , ou utiliser une requête paramétrée. Les deux vous protégeraient de l'injection SQL. La requête paramétrée est considérée comme la meilleure pratique, mais il faudra passer à une nouvelle extension mysql en PHP avant de pouvoir l'utiliser.

Nous couvrirons la chaîne à impact inférieur échappant en premier.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Voir aussi les détails de la fonction mysql_real_escape_string .

Pour utiliser la requête paramétrée, vous devez utiliser MySQLi plutôt que les fonctions MySQL . Pour réécrire votre exemple, nous aurions besoin de quelque chose comme ce qui suit.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

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

En outre, comme d'autres l'ont suggéré, il peut être utile / plus facile d'intensifier une couche d'abstraction avec quelque chose comme PDO .

Veuillez noter que le cas sur lequel vous avez posé des questions est assez simple et que des cas plus complexes peuvent nécessiter des approches plus complexes. En particulier:

  • Si vous souhaitez modifier la structure du mysql_real_escape_string SQL en fonction des entrées de l'utilisateur, les requêtes paramétrées ne vont pas vous aider et l'échappement requis n'est pas couvert par mysql_real_escape_string . Dans ce type de cas, il serait préférable de passer l'entrée de l'utilisateur dans une liste blanche pour s'assurer que seules les valeurs "sûres" sont autorisées.
  • Si vous utilisez des entiers provenant d'une entrée utilisateur dans une condition et que vous mysql_real_escape_string approche mysql_real_escape_string , vous souffrirez du problème décrit par Polynomial dans les commentaires ci-dessous. Ce cas est plus complexe car les entiers ne seraient pas entourés de guillemets. Vous pouvez donc régler le problème en validant que la saisie de l'utilisateur ne contient que des chiffres.
  • Il y a probablement d'autres cas dont je ne suis pas au courant. Vous trouverez peut-être que c'est une ressource utile sur certains des problèmes les plus subtils que vous pouvez rencontrer.

Opérateur de vaisseau spatial <=> (ajouté en PHP 7)

Exemples pour <=> opérateur de vaisseau spatial (PHP 7, Source: Manuel PHP):

Entiers, flotteurs, chaînes, tableaux et objets pour la comparaison à trois voies des variables.

// Integers
echo 10 <=> 10; // 0
echo 10 <=> 20; // -1
echo 20 <=> 10; // 1

// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1

echo "a" <=> "aa"; // -1
echo "zz" <=> "aa"; // 1

// Arrays
echo [] <=> []; // 0
echo [1, 2, 3] <=> [1, 2, 3]; // 0
echo [1, 2, 3] <=> []; // 1
echo [1, 2, 3] <=> [1, 2, 1]; // 1
echo [1, 2, 3] <=> [1, 2, 4]; // -1

// Objects
$a = (object) ["a" => "b"]; 
$b = (object) ["a" => "b"]; 
echo $a <=> $b; // 0

$a = (object) ["a" => "b"]; 
$b = (object) ["a" => "c"]; 
echo $a <=> $b; // -1

$a = (object) ["a" => "c"]; 
$b = (object) ["a" => "b"]; 
echo $a <=> $b; // 1

// only values are compared
$a = (object) ["a" => "b"]; 
$b = (object) ["b" => "b"]; 
echo $a <=> $b; // 1






php mysql sql security sql-injection