Как очистить 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, а символ кавычек вашего идентификатора РСУБД — ", то 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.

Причина, по которой вы не можете использовать prepareStatement, заключается в том, что он, вероятно, заключает имя таблицы в ' и экранирует его как строку.

person Fiid    schedule 05.11.2009