2009-11-04 3 views
15

Pour certaines instructions SQL, je ne peux pas utiliser un prêt statment, par exemple:Comment puis-je désinfecte SQL sans utiliser les commandes préparées

SELECT MAX(AGE) FROM ? 

Par exemple quand je veux faire varier la table. Y a-t-il un utilitaire qui assainit sql en Java? Il y en a un à ruby.

+6

Si le nom de la table est directement ** ** de l'entrée utilisateur, vous avez des problèmes beaucoup plus importants à vous soucier de votre SQL désinfectante (et si elle ne il n'y a rien à aseptiser) – ChssPly76

Répondre

2

Impossible. Le mieux que vous pouvez faire est d'utiliser String#format().

String sql = "SELECT MAX(AGE) FROM %s"; 
sql = String.format(sql, tablename); 

Notez que cela n'évite pas les risques d'injection SQL. Si le tablename est une valeur contrôlée par l'utilisateur/le client, vous devez le nettoyer à l'aide de String#replaceAll().

tablename = tablename.replaceAll("[^\\w]", ""); 

Espérons que cela aide.

[Modifier] Je devrais ajouter: ne PAS l'utiliser pour les valeurs de colonne où vous pouvez utiliser PreparedStatement pour. Continuez simplement à l'utiliser de la manière habituelle pour toutes les valeurs de colonne. [Edit2] Le mieux serait de ne pas laisser l'utilisateur/client être capable d'entrer le tablename comme il veut, mais mieux présenter une liste déroulante contenant tous les noms valables (que vous pouvez obtenir par DatabaseMetaData#getCatalogs()) dans l'interface utilisateur de sorte que l'utilisateur/client peut le sélectionner. N'oubliez pas de vérifier dans le côté serveur si la sélection est valide car on pourrait usurper les paramètres de la requête.

+0

@BalusC - +1 pour la référence d'injection SQL. –

0

Dans ce cas, vous pouvez valider le nom de la table par rapport à la liste des tables disponibles en obtenant la liste de tables à partir de DatabaseMetaData. En réalité, il serait probablement plus facile d'utiliser une regex pour supprimer des espaces, peut-être aussi quelques mots réservés sql, ";", etc de la chaîne avant d'utiliser quelque chose liek String.format pour construire votre instruction sql complète.

La raison pour laquelle vous ne pouvez pas utiliser prepareStatement est qu'il encapsule probablement le nom de la table dans '' s et l'échappe comme une chaîne.

17

Les paramètres de requête de requête préparés à droite peuvent être utilisés uniquement lorsque vous utilisez une valeur littérale unique . Vous ne pouvez pas utiliser un paramètre pour un nom de table, un nom de colonne, une liste de valeurs ou toute autre syntaxe SQL.

Vous devez donc interpoler votre variable d'application dans la chaîne SQL et la citer correctement. N'utiliser citant pour délimiter l'identifiant de votre nom de la table, et échapper à la chaîne de citation en le doublant:

java.sql.DatabaseMetaData md = conn.getMetaData(); 
String q = md.getIdentifierQuoteString(); 
String sql = "SELECT MAX(AGE) FROM %s%s%s"; 
sql = String.format(sql, q, tablename.replaceAll(q, q+q), q); 

Par exemple, si votre nom de la table est littéralement table"name, et votre identifiant SGBDR guillemet est ", puis sql doit contenir une chaîne comme:

SELECT MAX(AGE) FROM "table""name" 

Je suis également d'accord avec le commentaire de @ ChssPly76 - il est préférable que votre entrée utilisateur est en fait pas le nom de la table littérale, mais un signifiant que votre code cartes dans un nom de table, que vous alors inter polate dans la requête SQL. Cela vous donne plus d'assurance qu'aucune injection SQL ne peut avoir lieu.

HashMap h = new HashMap<String,String>(); 
/* user-friendly table name maps to actual, ugly table name */ 
h.put("accounts", "tbl_accounts123"); 

userTablename = ... /* user input */ 
if (h.containsKey(userTablename)) { 
    tablename = h.get(userTablename); 
} else { 
    throw ... /* Exception that user input is invalid */ 
} 
String sql = "SELECT MAX(AGE) FROM %s"; 
/* we know the table names are safe because we wrote them */ 
sql = String.format(sql, tablename); 
+0

+1 pour votre commentaire sur les cartes de code. C'est définitivement le chemin à parcourir. Tous les noms de tables peuvent être obtenus par DatabaseMetaData # getCatalogs() et être représentés comme une liste déroulante dans l'interface utilisateur. – BalusC

+0

Mais si vous donnez les vrais noms de tables dans une liste déroulante, vous devez toujours utiliser une carte pour convertir l'entrée de l'utilisateur en nom de table, car les entrées peuvent être truquées. Par exemple. Je peux taper une URL avec tout ce que je veux dans les paramètres de la requête, indépendamment de ce qui apparaît dans la liste déroulante. L'utilisation d'une carte est destinée à filtrer l'entrée après réception de la demande, et non avant qu'elle ne produise le formulaire d'interface utilisateur. –

Questions connexes