Category Archives: SQL

Изучаем SQL инъекции

Written by elwood

sql_injection

Наткнулся на прекрасный онлайн-квест https://www.alexbers.com/sql/. Прошёл его и очень всем рекомендую – я, например, хоть и знал, чем опасны SQL-инъекции, но вживую ни разу инъекцию не раскручивал. А тут как раз самое нужное – практические навыки !

После выполнения квеста в мозгу складывается довольно стройная картина. И вывод таков: при наличии уязвимости и самого маленького output’а на сайте (кол-во записей, или время выполнения запроса) из инъекции можно вытащить вообще всю базу целиком ! Грубо говоря, обладая одним битом информации и либерально настроенным mysql сервером, можно получить названия баз, таблиц, колонок и значений вообще всего 🙂

Кстати, кажется, до 5ой версии MySql это было невозможно, и приходилось угадывать и перебирать имена таблиц-колонок. Но теперь..

В общем, попробуйте, не пожалеете. За подсказками можно сходить на хабр : http://habrahabr.ru/post/253885/. Хотя там 8 и 9 решены кривовато – но в комментариях описан и “правильный” путь.

Борьба с native queries в EclipseLink 2.5 (Oracle DB)

Written by elwood

JPA Logo

Предыстория: у меня был проект – веб-приложение, работающее в GlassFish. Приложение собиралось в EAR а слой данных был реализован на базе JPA. В качестве реализации JPA использовался EclipseLink 2.5, встроенный в GlassFish 4.0. База данных – Oracle 11g Express Edition.

Вообще, до этого я всегда работал с Hibernate, и EclipseLink заставил немного помучаться с особенностями восприятия JPQL. Например, EclipseLink не хотел понимать count(*) и все используемые таблицы обязывал снабжать алиасами. Но это мелочи, всё было хорошо, пока не понадобилось сделать несложный запрос: получить объекты с условием, в котором было необходимо применение арифметики с датами. Итак, краткая история мучений.

JPQL: OPERATOR()

Вычитал, что в EclipseLink JPQL можно использовать оператор OPERATOR('AddDate') тынц. Как оказалось, в EclipseLink этот оператор реализован не был. Что в принципе удивительно, т.к. вендором EclipseLink сейчас является Oracle, и нормальный человек в этом случае имеет все основания ожидать хорошей поддержки их СУБД.

JPQL: SQL()

Попробовал также и SQL(), позволяющий вставлять в JPQL вставки нативного SQL тынц. К сожалению, и тут не получилось. Результирующий SQL был с багом, говорящем о несоответствии количества открывающих и закрывающих скобок.

Пробуем @NamedNativeQuery

Отчаявшись решить проблему в рамках JPQL, решил спуститься на ступеньку ниже и заюзать Native Query. Но запрос мой требовал соединения четырёх сущностей, и некоторые поля в них назывались по умолчанию одинаково (ID). Аннотация @EntityResult позволяет для таких ситуаций задать discriminatorColumn, но мне не удалось заставить его работать. Маппинг работал некорректно, значения пропертей маппились некорректно в любом варианте использования. Более того, выяснилось, что все столбцы должны иметь уникальный префикс. Иначе вложенные объекты могут захватить свойство (например, ID) внешнего объекта, чей столбец ID был без префикса. Всё это выглядит настолько бажным, что становится странным, как такое количество багов может считаться приемлемым.

Работающий маппинг теперь имел вид:

@SqlResultSetMapping( name = "request-department", entities = {
        @EntityResult( entityClass = Request.class, fields = {
                @FieldResult( name = "id", column = "R_ID"),
                @FieldResult( name = "deadlineDate", column = "R_DEADLINE_DATE"),
                @FieldResult( name = "correctedDeadlineDate", column = "R_CORRECTED_DEADLINE_DATE")
        }),
        @EntityResult( entityClass = Department.class, fields = {
                @FieldResult( name = "id", column = "DEP_ID"),
                @FieldResult( name = "description", column = "DEP_DESCRIPTION"),
                @FieldResult( name = "name", column = "DEP_NAME")
        }),
        @EntityResult( entityClass = User.class, fields = {
                @FieldResult( name = "id", column = "U_ID"),
                @FieldResult( name = "email", column = "U_EMAIL"),
                @FieldResult( name = "personalFirstName", column = "U_FIRST_NAME"),
                @FieldResult( name = "personalLastName", column = "U_LAST_NAME"),
                @FieldResult( name = "personalMiddleName", column = "U_MIDDLE_NAME")
        }),
        @EntityResult( entityClass = User.class, fields = {
                @FieldResult( name = "id", column = "ASS_ID"),
                @FieldResult( name = "email", column = "ASS_EMAIL"),
                @FieldResult( name = "personalFirstName", column = "ASS_FIRST_NAME"),
                @FieldResult( name = "personalLastName", column = "ASS_LAST_NAME"),
                @FieldResult( name = "personalMiddleName", column = "ASS_MIDDLE_NAME")
        })
})

Left join и вложенные объекты

Тут я вспомнил, что мне на самом деле нужен не INNER JOIN, а LEFT JOIN. То есть мне нужно сделать один из @EntityResult – nullable. Но этого сделать нельзя ! EclipseLink видит, что User.Id – первичный ключ, и падает на утверждении, что он не может быть Null. Ничего нельзя сделать, кроме того, что подставлять в столбец магическое число, сигнализирующее о том, что на самом деле сущность отсутствует. Вложенные объекты, кстати, тоже подцепить не удаётся. Но можно их зацепить отдельно, а потом вручную присвоить свойствам внешнего объекта.

http://www.eclipse.org/forums/index.php/t/305321/ https://www.java.net/node/675607

Параметры

Ок, после явного прописывания всех столбцов и использования -1 для сигнализации null-объектов, все работало. Теперь я подумал, что неплохо бы добавить в мой Named Native Query параметры. Как оказалось, это невозможно. Параметры в named native queries не работают вообще и никак. Не работает ничего: ни именование с двоеточия, ни вопросиками, ни через решётку (как предлагают в одном из ответов).

http://eclipse.1072660.n5.nabble.com/Params-on-NamedNativeQuery-td3401.html

Пришлось убрать аннотацию @NamedNativeQuery, и сделать всё руками:

Query nativeQuery = entityManager.createNativeQuery(
    String.format( sql, daysBeforeDeadline, RequestStatus.Sent.ordinal(), RequestStatus.Processing.ordinal() ),
    "request-department" );
List<Object[]> resultList = nativeQuery.getResultList();
 
for ( Object[] rowObjects : resultList ) {
    Request request = ( Request ) rowObjects[0];
    Department department = ( Department ) rowObjects[1];
    User user = ( User ) rowObjects[2];
    User assignee = (User) rowObjects[3];
    request.setDepartment( department );
    request.setUser( user );
    if (assignee.getId() != -1)
    request.setAssignee( assignee );
    requests.add( request );
}
return requests;

Прикол в том, что и здесь параметры тоже не работают ! Приходится вставлять их с помощью String.format().

(Тут вроде у мужиков работает, но у меня и так не заработало)

Заключение

В общем, я рад, что таки-получилось заставить это работать, но какой ценой ? Программистам EclipseLink стоит задуматься о качестве своего продукта, если на такие простые вещи за столько лет разработки у них не отработаны работающие сценарии на одной из самых популярных СУБД, тем более что они теперь – вендоры этой ORM. Но если вы желаете использовать в своём проекте native queries, то лучше придерживаться следующих правил:

  • Не использовать @NamedNativeQuery
  • Явно задавать все имена столбцы, они должны быть уникальны и не должны совпадать по названию ни с одним из свойств вложенных сущностей. Лучше всего задать уникальные префиксы для каждой сущности.
  • Там, где ожидаются nullable-столбцы, но которые мапятся на свойства, не допускающие значений null, придётся использовать магические константы или другие столбцы в качестве флагов
  • Параметры не работают – придётся вставлять их вручную

Как проверить базовое знание SQL

Written by elwood

На днях вспомнил об одной задаче, которая мне попалась на работе года полтора назад.
В то время я еще не знал об sql-ex.ru и мои познания в SQL были достаточно слабыми, на уровне
простейших селектов с inner join’ами. И, как я помню тогда, я не смог решить эту задачу без
дополнительного подзапроса. Сейчас, после нескольких десятков решенных задач на sql-ex.ru я
без проблем с ней справился, что навело меня на мысль о том, что это – неплохой тест на знание
азов SQL. В отличие от более сложных задач, эта задачка не требует большого количества времени, но
покрывает почти всё, что требуется для среднего программера в вопросе знания SQL, особенно если решать её в уме. Сразу отпадают вопросы наподобие “что такое JOIN, чем LEFT JOIN отличается от INNER JOIN, как работает группировка” итд.

Итак, формулировка такова. Есть 2 таблицы – Collection и Item.

Необходимо сделать выборку
Collection.ID Collection.Name Collection.Count
тех коллекций, которые содержат меньше 5 элементов.
Вот и всё.

ДАЛЬШЕ СПОЙЛЕР, НЕ ЧИТАТЬ !!!111 (Я не научился пока скрывать в вордпрессе куски постов, но как только научусь – оформлю соответствующе).

Алгоритм решения задачи.
Ну ясно же – надо сделать JOIN, делаем JOIN, группируем по Collection.ID, выводим ID, Name, Count(*).
Черт. При группировке по ID нельзя вывести Name. Значит, надо либо завернуть полученную выборку в еще 1 запрос, либо дописать в группировку этот Name. Ок, работает. Только пустые коллекции не выводятся. Почему ? Ну ясно же – они не попадают в результат INNER JOIN’a, надо использовать LEFT JOIN. О, теперь получилось. Только коллекции-то пустые, а count(*) выводит единицу вместо нуля. Ах да, это же LEFT JOIN, он джойнит Collection.ID Collection.Name NULL NULL и count(*) дает единицу. Как поправить-то. Вычесть 1 нельзя, поскольку так можно запороть непустые коллекции. Надо как-то проверить, является ли коллекция пустой, и для нее вывести 0, а для остальных – count(*). Да у нас же справа NULL, можно их в какую-нибудь агрегатную функцию запихнуть, и сравнить с NULL’ом. Если NULL, значит, коллекция пустая, и для нее выводим 0, иначе – count(*). Эврика! Ну и добавляем HAVING. Пишем запрос:

SELECT
  c.ID, c.Name, CASE WHEN COUNT(*) = 1 AND MAX(i.CollectionID) IS NULL THEN 0 ELSE COUNT(*) END
FROM Collection c
LEFT JOIN Item i ON c.ID = i.CollectionID
GROUP BY c.ID, c.Name
HAVING COUNT(*) < 5