Как да дезинфекцирам SQL, без да използвам подготвени изрази

За някои sql изрази не мога да използвам подготвен израз, например:

SELECT MAX(AGE) FROM ?

Например, когато искам да променя масата. Има ли помощна програма, която дезинфекцира sql в Java? Има един в рубин.


person mr.gaffo    schedule 04.11.2009    source източник
comment
Ако името на таблицата идва директно от въвеждането на потребителя, имате много по-големи проблеми, за които да се тревожите, отколкото да дезинфекцирате вашия SQL (и ако това не стане, няма какво да дезинфекцирате)   -  person ChssPly76    schedule 05.11.2009


Отговори (3)


Точно така, параметрите на заявката за подготвен израз могат да се използват само там, където бихте използвали единична литерална стойност. Не можете да използвате параметър за име на таблица, име на колона, списък със стойности или друг SQL синтаксис.

Така че трябва да интерполирате променливата на вашето приложение в SQL низа и да поставите низа в кавички по подходящ начин. Използвайте кавичка, за да ограничите идентификатора на името на вашата таблица, и избягвайте низа с кавички, като го удвоите:

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);

Например, ако името на вашата таблица е буквално table"name, а символът за кавички на вашия RDBMS идентификатор е ", тогава sql трябва да съдържа низ като:

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

Също така съм съгласен с коментара на @ChssPly76 -- най-добре е вашият потребителски вход всъщност да не е буквалното име на таблицата, а означаващо, че кодът ви преобразува в име на таблица, което след това интерполирате в SQL заявката. Това ви дава повече увереност, че не може да се случи инжектиране на SQL.

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); 
person Bill Karwin    schedule 05.11.2009
comment
+1 за вашия коментар относно кодовите карти. Това определено е правилният начин. Всички имена на таблици могат да бъдат получени от DatabaseMetaData#getCatalogs() и да бъдат представени като падащо меню в потребителския интерфейс. - person BalusC; 05.11.2009
comment
Но ако дадете истинските имена на таблици в падащо меню, все пак трябва да използвате карта, за да конвертирате въведеното от потребителя в име на таблица, защото въвеждането може да бъде фалшиво. напр. Мога да въведа URL адрес с всичко, което искам в параметрите на заявката, независимо от това, което се появява в падащото меню. Използването на карта има за цел да филтрира входа, след като получи заявката, а не преди да изведе формата на потребителския интерфейс. - person Bill Karwin; 05.11.2009

Невъзможно. Най-доброто, което можете да направите, е да използвате String#format().

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

Имайте предвид, че това не избягва рисковете от SQL инжектиране. Ако tablename е стойност, контролирана от потребител/клиент, ще трябва да я дезинфекцирате с помощта на String#replaceAll().

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

Надявам се това да помогне.

[Редактиране] Трябва да добавя: НЕ използвайте това за стойности на колони, за които можете да използвате PreparedStatement. Просто продължете да го използвате по обичайния начин за всички стойности на колони.

[Edit2] Най-добре би било да не позволявате на потребителя/клиента да може да въвежда името на таблицата както иска, а по-добре да представите падащо меню, съдържащо всички валидни имена на таблици (които можете да получите от DatabaseMetaData#getCatalogs()) в потребителския интерфейс, така че потребителят/клиентът може да го изберете. Не забравяйте да проверите от страната на сървъра дали изборът е валиден, защото някой може да подмени параметрите на заявката.

person BalusC    schedule 04.11.2009
comment
@BalusC - +1 за справката за SQL инжектиране. - person Michael Riley - AKA Gunny; 05.11.2009

В този случай можете да потвърдите името на таблицата спрямо списъка с налични таблици, като получите списъка с таблици от DatabaseMetaData. В действителност вероятно ще бъде по-лесно да използвате регулярен израз за премахване на интервали, може би също и някои sql запазени думи, ";", и т.н. от низа, преди да използвате нещо като String.format, за да изградите вашия пълен sql израз.

Причината, поради която не можете да използвате createdStatement, е, че той вероятно затваря името на таблицата в ''s и го екранира като низ.

person Fiid    schedule 05.11.2009