Échappement des noms de colonnes dans les instructions PDO
Je construis actuellement une requête où les parties champ / colonne et valeur sont éventuellement constituées de données entrées par l'utilisateur.
Le problème est d'échapper aux noms de champs. J'utilise des instructions préparées afin d'échapper correctement et de citer les valeurs, mais en échappant aux noms de champs, j'ai des problèmes.
- mysql_real_escape_string nécessite une ressource de connexion mysql pour nous, ce qui est exclu
- PDO:: quote ajoute des guillemets autour des noms de champs qui les rendent inutile dans une requête aussi
- addslashes fonctionne mais n'est pas vraiment sûr
N'importe qui a une idée sur le meilleur moyen d'insérer correctement les noms de champs dans la requête avant de le transmettre à PDO::prepare?
5 réponses
La manière standard ANSI de faire un identifiant délimité est:
SELECT "field1" ...
Et s'il y a un " dans le nom, doublez-le:
SELECT "some""thing" ...
Malheureusement, cela ne fonctionne pas dans MySQL avec les paramètres par défaut, car MySQL préfère penser que les guillemets doubles sont une alternative aux guillemets simples pour les littéraux de chaîne. Dans ce cas, vous devez utiliser des backticks (comme indiqué par Björn) et une barre oblique inverse.
Pour faire un échappement antislash correctement, vous auriez {[10] } besoin de mysql_real_escape_string, parce que c'est dépendant du jeu de caractères. Mais le point est discutable, car ni mysql_real_escape_string ni addslashes n'échappent au caractère backquote. Si vous pouvez être sûr qu'il n'y aura jamais de caractères non-ASCII dans les noms de colonnes, vous pouvez vous en sortir avec une barre oblique inverse manuelle-échappant aux caractères ' et\.
De toute façon, ce n'est pas compatible avec d'autres bases de données. Vous pouvez dire à MySQL d'autoriser la syntaxe ANSI en définissant L'option de configuration ANSI_QUOTES. De même, SQL Server s'étouffe également sur les guillemets doubles par défaut; il utilise encore une autre syntaxe, à savoir les crochets. Encore une fois, vous pouvez le configurer pour prendre en charge la syntaxe ANSI avec l'option’ quoted_identifier'.
Résumé: si vous avez seulement besoin de compatibilité MySQL:
A. Utilisez backquotes et refusez le caractère backquote, backslash et nul dans les noms car leur échappement n'est pas fiable
Si vous avez besoin d'une compatibilité SGBD croisée, soit:
B. utiliser des guillemets doubles et exiger des utilisateurs MySQL/SQL-Server pour modifier la configuration de manière appropriée. Interdire les caractères de guillemets doubles dans le nom (comme Oracle ne peut pas les gérer même échappé). Ou,
C. ayez un paramètre pour MySQL vs SQL Server vs Others, et produisez la syntaxe backquote, Square bracket ou double-quote en fonction de cela. Interdire les guillemets doubles et la barre oblique inverse/backquote/nul.
C'est quelque chose que vous espérez que la couche d'accès aux données aurait une fonction, mais PDO ne le fait pas.
Résumé du résumé: les noms de colonnes arbitraires sont un problème, à éviter si vous pouvez l'aider.
Résumé du résumé du résumé: gnnnnnnnnnnnh.
La bonne réponse est
str_replace("`", "``", $fieldname)
Faux:
mysql> SELECT `col\"umn` FROM user; ERROR 1054 (42S22): Unknown column 'col\"umn' in 'field list'
Droite:
mysql> SELECT `kid``s` FROM user; ERROR 1054 (42S22): Unknown column 'kid`s' in 'field list' mysql> SELECT ```column``name``` FROM user; ERROR 1054 (42S22): Unknown column '`column`name`' in 'field list'
(Notez que dans le dernier exemple, le nom de la colonne a 3 (trois) dos extra-tiques, juste pour montrer un cas extrême)
Cela peut affecter les performances, mais il doit être sécurisé.
Exécutez D'abord une requête de table DESCRIBE pour obtenir une liste de noms de champs Autorisés, puis faites correspondre ces agaisnt les données soumises par l'utilisateur.
S'il y a une correspondance, vous pouvez utiliser les données soumises par l'utilisateur sans avoir besoin d'échappement.
S'il n'y a pas de correspondance, c'est une faute de frappe ou un hack - de toute façon c'est une "erreur" dans les données entrées et la requête ne doit pas être exécutée.
La même chose pourrait être faite pour la table 'dynamic' noms en exécutant une requête SHOW TABLES et en faisant correspondre à partir de ce jeu de résultats.
Dans une de mes applications, j'ai un script 'install' ; une partie de cela interroge les noms des champs de la base de données et de la table, puis écrit un fichier php auquel il est toujours renvoyé, donc je n'exécute pas constamment des requêtes de description contre la base de données, par exemple
$db_allowed_names['tableName1']['id'] = 1;
$db_allowed_names['tableName1']['field1'] = 1;
$db_allowed_names['tableName1']['field2'] = 1;
$db_allowed_names['tableName2']['id'] = 1;
$db_allowed_names['tableName2']['field1'] = 1;
$db_allowed_names['tableName2']['field2'] = 1;
$db_allowed_names['tableName2']['field3'] = 1;
if($db_allowed_names['tableName1'][$_POST['field']]) {
//ok
}
J'utilise des clés de tableau comme ceci car l'instruction if est un peu plus rapide qu'une recherche in_array
Que diriez-vous de quelque chose comme ça?
function filter_identifier($str, $extra='') {
return preg_replace('/[^a-zA-Z0-9_'.$extra.']/', '', $str);
}
try {
$res = $db->query('SELECT '.filter_identifier($_GET['column'], '\*').' FROM '.filter_identifier($_GET['table']).' WHERE id = ?', $id);
} catch (PDOException $e) {
die('error querying database');
}
Il s'agit d'une simple liste de caractères basée sur une liste blanche. Tous les caractères qui ne figurent pas dans la liste seront supprimés. Heureusement pour moi, j'ai pu faire la base de données et les tables, donc je sais qu'il n'y aura jamais de caractères en dehors de "A-zA-Z0-9_" (note: pas d'espace). Vous pouvez ajouter des caractères supplémentaires à la liste via l'arg $ extra. Si quelqu'un essayait de mettre "(SELECT * FROM users);-- " dans 'column' , il filtrerait jusqu'à "SELECT * FROMusers" , ce qui lancerait une exception:)
J'essaie d'éviter de faire des requêtes supplémentaires si possible (je suis très sensible aux performances). Donc, des choses comme faire une description à l'avance ou coder en dur un tableau de tables/colonnes à vérifier est quelque chose que je préfère ne pas faire.
Conception Bizarre d'un projet, mais pour votre problème: entourez vos noms de champs avec ` et utilisez également addslashes pour le nom.
select `field1`, `field2` from table where `field3`=:value