Что такое SQL injection и как защититься от него в Java?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
SQL Injection и защита от неё в Java
SQL Injection — это один из самых опасных типов уязвимостей веб-приложений, когда злоумышленник внедряет SQL команды в пользовательский ввод для выполнения несанкционированных операций с базой данных.
Что такое SQL Injection?
SQL Injection происходит, когда приложение конкатенирует пользовательский ввод напрямую в SQL запрос без должной санитизации или параметризации.
Пример уязвимого кода:
String username = userInput; // например: "admin' --"
String password = userInput; // например: "anything"
String sql = "SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(sql);
Если пользователь введёт username = "admin' --", итоговый SQL будет:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
Комментарий -- скроет проверку пароля, и злоумышленник войдёт как admin!
Примеры атак
1. Аутентификация обхода (Login bypass):
username: admin' --
password: anything
Результат SQL:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
Пароль не проверяется!
2. Утечка данных (Data exfiltration):
username: ' OR '1'='1
password: ' OR '1'='1
Результат SQL:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '' OR '1'='1'
Вернёт ВСЕ пользователей!
3. Удаление данных (Data destruction):
username: admin'; DROP TABLE users; --
password: anything
Результат SQL:
SELECT * FROM users WHERE username = 'admin'; DROP TABLE users; --' AND password = 'anything'
Таблица users будет удалена!
Защита 1: Подготовленные операторы (Prepared Statements)
ЭТО ГЛАВНЫЙ СПОСОБ ЗАЩИТЫ!
Подготовленные операторы разделяют SQL код от данных. Параметры передаются отдельно и не интерпретируются как SQL код.
public class SafeDatabaseAccess {
/**
* ПРАВИЛЬНО: Использование PreparedStatement
*/
public User authenticateUser(String username, String password) throws SQLException {
String sql = "SELECT id, username, email FROM users WHERE username = ? AND password = ?";
try (PreparedStatement preparedStatement =
connection.prepareStatement(sql)) {
// Параметры передаются безопасно, не как SQL код
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
return new User(
resultSet.getLong("id"),
resultSet.getString("username"),
resultSet.getString("email")
);
}
}
return null;
}
/**
* НЕПРАВИЛЬНО: Конкатенация строк (УЯЗВИМО!)
*/
public User authenticateUserUnsafe(String username, String password)
throws SQLException {
String sql = "SELECT id, username, email FROM users WHERE username = '" +
username + "' AND password = '" + password + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
if (resultSet.next()) {
return new User(
resultSet.getLong("id"),
resultSet.getString("username"),
resultSet.getString("email")
);
}
return null;
}
}
Защита 2: ORM (Object-Relational Mapping)
ORM фреймворки как Hibernate и JPA автоматически используют параметризованные запросы.
// Hibernate / JPA
@Repository
public class UserRepository extends JpaRepository<User, Long> {
// БЕЗОПАСНО: JPA автоматически параметризует запрос
User findByUsernameAndPassword(String username, String password);
}
// Использование
User user = userRepository.findByUsernameAndPassword("admin", "password123");
JPQL с параметрами:
@Repository
public class UserRepository {
@PersistenceContext
private EntityManager em;
// ПРАВИЛЬНО: Параметризованный JPQL запрос
public User findByUsername(String username) {
String jpql = "SELECT u FROM User u WHERE u.username = :username";
return em.createQuery(jpql, User.class)
.setParameter("username", username)
.getResultList()
.stream()
.findFirst()
.orElse(null);
}
}
НЕПРАВИЛЬНО (даже в JPQL):
// УЯЗВИМО!
String jpql = "SELECT u FROM User u WHERE u.username = '" + username + "'";
em.createQuery(jpql, User.class).getResultList();
Защита 3: Валидация входных данных
Валидируйте на клиенте и сервере:
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// Валидация 1: Проверка наличия данных
if (request.getUsername() == null || request.getPassword() == null) {
throw new BadRequestException("Username and password required");
}
// Валидация 2: Проверка длины
if (request.getUsername().length() > 50 ||
request.getPassword().length() > 100) {
throw new BadRequestException("Invalid input length");
}
// Валидация 3: Проверка формата (например, для email)
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
if (!request.getUsername().matches(emailRegex)) {
throw new BadRequestException("Invalid username format");
}
// Только после валидации вызываем безопасный метод с PreparedStatement
return userService.authenticateUser(request.getUsername(),
request.getPassword());
}
}
Защита 4: Минимальные привилегии БД
Создайте пользователя БД с минимальными правами:
-- Создание пользователя только для SELECT/INSERT/UPDATE
CREATE USER app_user@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE ON database.* TO app_user@'localhost';
-- БД пользователь НЕ имеет DROP/ALTER/DELETE
-- Таже если атакующий внедрит DROP, он не сработает
Защита 5: Использование хранимых процедур
С параметрами (безопасно):
public void callStoredProcedure(String username) throws SQLException {
String sql = "{CALL authenticate_user(?)}";
try (CallableStatement callableStatement =
connection.prepareCall(sql)) {
callableStatement.setString(1, username); // Параметр безопасен
callableStatement.execute();
}
}
Без параметров (уязвимо):
// НЕПРАВИЛЬНО!
String sql = "{CALL authenticate_user('" + username + "')}";
CallableStatement callableStatement = connection.prepareCall(sql);
Защита 6: Логирование и мониторинг
@Component
public class SqlInjectionDetector {
private static final List<String> SUSPICIOUS_PATTERNS = Arrays.asList(
"'", "\"", ";", "--", "/*", "*/", "xp_", "sp_", "DROP", "DELETE",
"UNION", "SELECT", "INSERT", "UPDATE", "EXEC"
);
public void validateInput(String input) {
for (String pattern : SUSPICIOUS_PATTERNS) {
if (input.toUpperCase().contains(pattern)) {
logger.warn("Potential SQL injection attempt: {}", input);
throw new SecurityException("Invalid input detected");
}
}
}
}
Полный пример безопасного приложения
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
/**
* БЕЗОПАСНЫЙ метод аутентификации
*/
@Transactional(readOnly = true)
public Optional<User> authenticateUser(String username, String password) {
// 1. Получение пользователя (JPA параметризует запрос)
User user = userRepository.findByUsername(username);
if (user == null) {
return Optional.empty();
}
// 2. Сравнение хешированного пароля
if (passwordEncoder.matches(password, user.getPasswordHash())) {
return Optional.of(user);
}
return Optional.empty();
}
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// БЕЗОПАСНО: JPA параметризует запрос
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsername(@Param("username") String username);
}
Сравнение подходов
| Подход | Безопасно | Примечание |
|---|---|---|
| Конкатенация строк | ❌ | УЯЗВИМО - НИКОГДА не используй |
| PreparedStatement | ✅ | Лучший вариант |
| JPA/Hibernate | ✅ | ORM автоматически параметризует |
| Валидация входа | ⚠️ | Дополнительная защита, не основная |
| Хранимые процедуры | ✅ | Если использовать параметры |
| Логирование | ⚠️ | Помогает обнаружить атаки |
OWASP Top 10
SQL Injection занимает A03:2021 в OWASP Top 10 списке самых критичных уязвимостей веб-приложений.
Золотые правила
- НИКОГДА не конкатенируй пользовательский ввод в SQL
- ВСЕГДА используй PreparedStatement или ORM
- ВСЕГДА валидируй входные данные
- Используй параметризованные запросы везде — в Query, JPQL, SQL
- Логируй подозрительные попытки
- Давай БД пользователю минимальные права
- Обновляй зависимости регулярно
SQL Injection легко предотвратить, если использовать правильные инструменты и практики. Это ОБЯЗАТЕЛЬНОЕ знание для любого Java разработчика.