SQL-инъекции
SQL-инъекция возникает, когда непроверенные данные становятся частью текста SQL-запроса. База не отличает «значение поля» от добавленного пользователем SQL и исполняет всё как одну команду.
Уязвимый код
String login = request.getParameter("login");
String sql = "SELECT id FROM app_user WHERE login = '" + login + "'";
try (Statement statement = connection.createStatement();
ResultSet result = statement.executeQuery(sql)) {
// ...
}Значение ' OR '1'='1 меняет смысл WHERE. Экранирование кавычек вручную не решает задачу надёжно: появляются новые типы данных, кодировки и забытые ветки сборки запроса.
Параметризованный запрос
String sql = """
SELECT id, login
FROM app_user
WHERE login = ? AND status = ?
""";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, login);
statement.setString(2, status);
try (ResultSet result = statement.executeQuery()) {
// чтение результата
}
}PreparedStatement передаёт структуру команды и значения раздельно. Введённая строка остаётся значением параметра, а не фрагментом SQL. Тот же принцип используют именованные параметры Spring JDBC и параметры JPQL.
Что нельзя параметризовать
Плейсхолдер заменяет значение, но не имя таблицы, столбца и не направление сортировки. Такие части выбирают из закрытого списка:
String orderBy = switch (sort) {
case "created" -> "created_at";
case "name" -> "login";
default -> "id";
};После этого допустимо подставить orderBy в текст запроса: значение получено не от пользователя напрямую, а из allowlist.
Дополнительная защита
- выдать приложению только необходимые права в БД;
- не показывать клиенту текст SQL и стек драйвера;
- ограничивать размер и формат входных данных;
- проверять нативные запросы, динамические фильтры и
ORDER BY; - тестировать репозитории значениями с кавычками и управляющими символами.
ORM сама по себе не защищает: конкатенация в JPQL или native SQL остаётся уязвимой.
На собеседовании
Почему одной валидации недостаточно? Она зависит от бизнес-правил и может быть обойдена; параметризация устраняет смешение кода и данных на уровне драйвера.
Защищает ли PreparedStatement при конкатенации? Только параметры ?. Если пользовательский фрагмент уже вставлен в SQL-строку, защиты для него нет.
Связанные заметки: JDBC, SQL, Spring Data JPA.