null

Предотвращение SQL-инъекций в JAVA

SQL-инъекции или нарушение в целостности структуры SQL-запроса являются одними из самых распространённых и уязвимостей в вопросе безопасности. Используя SQL-инъекции злоумышленники  могут получить полный доступ к базам данных, персональным данным пользователей, могут удалить или изменить данные и даже таблицы. 

В основном опасность SQL-инъекций возникает при динамической генерации запросов к базе и использовании пользовательских данных. 

Пусть у нас есть таблица с заказами и мы будем реализовывать в ней поиск по названию:
 

public List<Orders> findOrdersByName(String enteredName) throws SQLException {
   String sql = "select * from orders "
           + " where name like '"
           + enteredName
           + "'";
   Connection c = dataSource.getConnection();
   return c.createStatement().executeQuery(sql);
}

Приведенный выше пример - плох. В этом случае злоумышленник имеет доступ к базам через поле enteredName. Например, введя в поле строку 

’; drop table orders; --


можно совсем удалить таблицу - после объединения одинарная кавычка в начале будет соответствовать той, которая уже есть в запросе. Два тире в конце означают, что все после них будет интерпретироваться как комментарий. Между - можно написать любой запрос. Таким образом, окончательный запрос будет таким

select * from orders where name like ' ’ ; drop table orders; -- '

 

Исправит ли ситуацию использование JPA? Не всегда. В приведенном ниже варианте использование JPA ничего не меняет. 

public List<Order> findOrdersByName(String enteredName) {
       return entityManager.createQuery("select * from orders “
+ ”where name like '" + enteredName + "'";
                  Order.class)
               .getResultList());
}

Как сделать лучше? 
 

 

1. Использовать параметризованные запросы

 

Достаточно добавить минимальные изменения в прошлый пример с JPA, которые помогут безопасно вставлять введенные пользователем значения в запрос перед его выполнением:
 

public List<Order> findOrdersByName(String enteredName) {
       return entityManager.createQuery("select * from orders “
+ ”where name like ':enteredName'";
                  Order.class)
   .setParameter("enteredName", enteredName)
               .getResultList());
}
 

Мы добавили параметр :enteredName в запрос и позволили EntityManager’у самому безопасно подставить введенную пользователем строку: .setParameter("enteredName", enteredName). Только и всего :)

 

Также можно изменить и первый пример и использовать параметры, вместо конкатенации строк втупую. Уберем конкатенацию и заменим ее на параметр “?”. И потом установим параметр при помощи p.setString(1, enteredName):
 


public List<Orders> findOrdersByName(String enteredName) throws SQLException {
   String sql = "select * from orders "
           + " where name like ?";
   Connection c = dataSource.getConnection();
   PreparedStatement p = c.prepareStatement(sql);
   p.setString(1, enteredName);
   return p.executeQuery(sql));
}

Минус этого метода в том, что строить динамические запросы становится затруднительно: придется строить сложную логику для управления параметрами, особенно, если их много и у них сложная логика установки.
 

 

2. Использование JPA Criteria API

 

Criteria API - это обращение к базе на более высоком уровне абстракции. Здесь не приходится напрямую использовать SQL-запросы, но нужно собирать их по кусочкам. Для организации работы с базой приходится использовать больше строк кода, но при помощи Criteria API безопасные динамические запросы генерировать становится проще.
 

public List<Orders> findOrdersByName(String enteredName) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Order> cq = cb.createQuery(Order.class);
    Root<Order> root = cq.from(Order.class);
    cq.select(root).where(cb.equal(root.get(Order_.name), enteredName));

    return entityManager.createQuery(q).getResultList();
}

 

3. Хранимые процедуры

 

Хранимые процедуры не всегда защищены от SQL-инъекций. Однако некоторые стандартные конструкции программирования хранимых процедур имеют тот же эффект, что и использование параметризованных запросов.


В следующем примере кода используется CallableStatement, реализация интерфейса хранимой процедуры в Java, для выполнения того же запроса к базе данных. Хранимая процедура sp_getAccountBalance должна быть предварительно определена в базе данных.
 


public List<Orders> findOrdersByName(String enteredName) {
        CallableStatement cs = connection.prepareCall("{call     sp_getOrdersByName(?)}");
  cs.setString(1, enteredName);
  return cs.executeQuery().getResultList();
}

 

4. Обработка пользовательских данных

 

Здесь есть 2 общепринятых способа: 

  1. Экранирование введенных пользователем данных
  2. Проверка введенных данных в запросах

Эти методы следует использовать только в крайнем случае, когда ни один из вышеперечисленных способов невозможен. Эффективность и отказоустойчивость приведенных методов будет напрямую зависеть от алгоритма и невозможно гарантировать, что она предотвратит все SQL-инъекции во всех ситуациях.