Защита от SQL инъекций в jdbc java

182
05 декабря 2017, 20:40

Часто вижу утверждения, что надо использовать PreparedStatement вместо обычного Statement, чтобы защититься от sql инъекций. Как он защищает?

Answer 1

Коротко, для нетерпеливых:

При использовании Statement строки запроса и значений складываются.

При использовании PreparedStatement имеется шаблон запроса и данные в него вставляются, с отражением кавычек.

Ниже подробнее с примерами.

Вступление.

Имеем такую простую таблицу с данными.

+-----------+----+--------+
| userName  | id |  pass  |
+-----------+----+--------+
| admin     |  1 |  admin |
| user      |  2 |  pass  |
| chuchelo  |  3 |  elli  |
+-----------+----+--------+

Модель User, будет содержать имя и пароль, а так же метод логин, который спросит данные с консоли.

class UserLogin {
    String name;
    String pass;
    public UserLogin() {
    }
    public void login()  {
        BufferedReader reader = null;
        try{
            reader = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("user name: ");
            name = reader.readLine();
            System.out.println("pass: ");
            pass = reader.readLine();
       } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (reader != null)
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }

Метод, который будет работать с обычным Statement:

UserLogin user = new UserLogin();
user.login();
try (Connection connect = MyConnection.getConnection()){
    Statement statement = connect.createStatement();
    String query = "SELECT userName, id, pass FROM users WHERE userName='" + user.name + "' AND pass = '" + user.pass + "'";
    System.out.println(query);
    ResultSet resultSet = statement.executeQuery(query);
    while (resultSet.next()){
        System.out.printf("User: id=%d name=%s pass=%s\n",
                resultSet.getInt("id"),
                resultSet.getString("userName"),
                resultSet.getString("pass"));
    }
    MyConnection.closeConnect();
} catch (SQLException e) {
    e.printStackTrace();
}

Теперь если мы запустим этот метод и введем в консоль данные без инъекции:

user name: admin
pass: admin
User: id=1 name=admin pass=admin

При этом сам запрос выглядит так:

 SELECT userName, id, pass FROM users WHERE userName='admin' AND pass = 'admin'

Если допустить ошибку в имени или пароле, то данные выведены не будет.

Теперь попробуем использовать инъекцию(' or'1'='1), т.е. введем такие данные:

user name: admin' or'1'='1
pass: blabla

То мы все равно получаем результат, несмотря на то, что пароль неверный:

 User: id=1 name=admin pass=admin

При этом сам запрос теперь выглядит так:

  SELECT userName, id, pass FROM users WHERE userName='admin' or'1'='1' AND pass = 'blabla'

т.к. выражение or'1'='1' всегда равно true, то даже без указания пароля мы получим все данные.

Как от этого защитит PreparedStatement?

Метод который будет получать данные из базы с помощью PreparedStatement:

UserLogin user = new UserLogin();
user.login();
try (Connection connect = MyConnection.getConnection()){
    String query = "SELECT userName, id, pass FROM users WHERE userName=? AND pass=?";
    PreparedStatement statement = connect.prepareStatement(query);
    statement.setString(1, user.name);
    statement.setString(2, user.pass);
    System.out.println(statement);
    ResultSet resultSet = statement.executeQuery();
    while (resultSet.next()){
        System.out.printf("User: id=%d name=%s pass=%s\n",
        resultSet.getInt("id"),
        resultSet.getString("userName"),
        resultSet.getString("pass"));
    }
    MyConnection.closeConnect();
} catch (SQLException e) {
    e.printStackTrace();
}

Все тоже самое, только заменили обычный Statement на PreparedStatement. Надеюсь вы на слово поверите, что при правильных данных мы получим верный результат, если нет то вот лог в консоли:

user name: user
pass: pass
User: id=2 name=user pass=pass
Запрос:
SELECT userName, id, pass FROM users WHERE userName='user' AND pass='pass'

А теперь попробуем использовать инъекцию:

user name: user' or'1'='1
pass: inject

И ответа не получаем, потому что запрос выглядит так:

  SELECT userName, id, pass FROM users WHERE userName='user\' or\'1\'=\'1' AND pass='inject'

Т.е. все кавычки были отражены слешем, инъекция не удалась.

Отличие Statement от PreparedStatement:

Statement - вы должны заботиться о кавычках в запросе и ставить их там где они нужны.

PreparedStatement - вставляет значения в запрос и за счет методов setString setInt и прочих. Он сам понимает где нужны кавычки, а где нет. Соответственно все входные данных оборачивает ими.

READ ALSO
Как перезапустить java программу на linux

Как перезапустить java программу на linux

У меня есть некий бот для телеграмма, как сделать, что бы он сам себя перезапускал?

171
Завершение телефонного звонка, программно

Завершение телефонного звонка, программно

У меня телефон Samsung J5 с android 60 подскажите, пошагово, как сбросить входящий телефонный звонок?

163
400 Bad Request, RESTTEMPLATE

400 Bad Request, RESTTEMPLATE

Использую android устройство и сервер на spring mvc + maven Пытаюсь выслать данные + файл на сервер при помощи RestTeamplateС просто данными все красиво вышло,...

179
Проблема при подсчете Double калькулятора

Проблема при подсчете Double калькулятора

При чтении строки функция доходит до точки и зависает

189