Часто вижу утверждения, что надо использовать PreparedStatement вместо обычного Statement, чтобы защититься от sql инъекций. Как он защищает?
Ответ
Коротко, для нетерпеливых:
При использовании 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
",
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
",
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 и прочих. Он сам понимает где нужны кавычки, а где нет. Соответственно все входные данных оборачивает ими.
Комментариев нет:
Отправить комментарий