SQL Injection: уязвимость, методы защиты и best practices
В этом материале разбираем полный спектр SQL Injection — от механики атак до практических методов защиты, Prepared Statements, ORM фреймворков и валидации входных данных с рабочими примерами.
Суть
SQL Injection происходит, когда злоумышленник внедряет вредоносные SQL-команды через поля ввода. Защищаются Prepared Statements, валидацией входа и ORM фреймворками.
Определение
SQL Injection — это техника атаки, при которой злоумышленник внедряет malicious SQL-команды в поля ввода приложения для получения несанкционированного доступа к базе данных или манипуляции данными.
Векторы атак:
- Формы авторизации: обход аутентификации
- Поля поиска: извлечение конфиденциальных данных
- URL-параметры: манипуляция запросами к базе
- Загрузка файлов: внедрение через имена файлов
- API-endpoints: прямое изменение данных в БД
Способы защиты:
- Prepared Statements: параметризованные запросы
- Валидация входа: проверка по whitelist
- ORM Framework: абстракция для доступа к БД
- Least Privilege: минимальные права на БД
- Error Handling: скрытие ошибок БД от пользователя
Ключевые пункты для проверок
- SQL Injection: внедрение команд через поля ввода
- Цели атак: извлечение, манипуляция, удаление данных
- Уязвимые точки: формы входа, поиск, параметры URL
- Защита: Prepared Statements, валидация, ORM
- Prepared Statements: параметризованные запросы с placeholders
- ORM Framework: Hibernate, Entity Framework, SQLAlchemy
- Least Privilege: минимальные права доступа к БД
- Релевантно для IHK: критический риск в веб-приложениях
Основные компоненты
- Векторы атак: возможные точки внедрения
- Техники injection: Union-based, Boolean-based, Time-based
- Методы защиты: Prepared Statements, валидация, ORM
- ORM Framework: слой абстракции для работы с БД
- Валидация входа: проверка данных по whitelist
- Обработка ошибок: безопасное логирование
- Security Headers: дополнительные слои защиты
- Мониторинг: обнаружение попыток атак
Примеры из практики
1. Атаки SQL Injection и противодействие
import java.sql.*;
import java.util.Scanner;
import java.util.regex.Pattern;
// Уязвимая реализация (подвержена SQL Injection)
class UnsafeLoginService {
private Connection connection;
public UnsafeLoginService(Connection connection) {
this.connection = connection;
}
public boolean loginUnsafe(String username, String password) {
// ОЧЕНЬ ОПАСНО - прямое конкатенирование строк
String query = "SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
System.out.println("Query: " + query);
try (Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
return rs.next(); // Успешный вход, если есть результат
} catch (SQLException e) {
System.err.println("Datenbankfehler: " + e.getMessage());
return false;
}
}
}
// Безопасная реализация с Prepared Statements
class SafeLoginService {
private Connection connection;
public SafeLoginService(Connection connection) {
this.connection = connection;
}
public boolean loginSafe(String username, String password) {
// БЕЗОПАСНО - Prepared Statement с placeholders
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
try (PreparedStatement pstmt = connection.prepareStatement(query)) {
// Установка параметров (автоматический escape)
pstmt.setString(1, username);
pstmt.setString(2, password);
try (ResultSet rs = pstmt.executeQuery()) {
return rs.next();
}
} catch (SQLException e) {
System.err.println("Datenbankfehler: " + e.getMessage());
return false;
}
}
// С валидацией входа
public boolean loginWithValidation(String username, String password) {
// Проверка входных данных перед запросом
if (!isValidUsername(username) || !isValidPassword(password)) {
System.err.println("Ungültige Eingabe");
return false;
}
return loginSafe(username, password);
}
private boolean isValidUsername(String username) {
// Whitelist: только буквы, цифры и underscore
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_]{3,20}$");
return pattern.matcher(username).matches();
}
private boolean isValidPassword(String password) {
// Минимум 8 символов, максимум 100
return password != null &&
password.length() >= 8 &&
password.length() <= 100;
}
}
// Расширенные меры безопасности
class AdvancedSecurityService {
private Connection connection;
public AdvancedSecurityService(Connection connection) {
this.connection = connection;
}
// С Rate Limiting
private Map<String, Integer> loginAttempts = new java.util.concurrent.ConcurrentHashMap<>();
private static final int MAX_ATTEMPTS = 5;
private static final long LOCK_TIME_MS = 30000; // 30 минут
public boolean loginWithRateLimit(String username, String password) {
String clientIp = getClientIp(); // Требуется реализация
// Проверка Rate Limiting
if (isBlocked(clientIp)) {
System.err.println("IP заблокирован из-за множественных попыток");
return false;
}
boolean loginSuccess = loginSafe(username, password);
if (!loginSuccess) {
incrementAttempts(clientIp);
} else {
clearAttempts(clientIp);
}
return loginSuccess;
}
private boolean isBlocked(String clientIp) {
Integer attempts = loginAttempts.get(clientIp);
return attempts != null && attempts >= MAX_ATTEMPTS;
}
private void incrementAttempts(String clientIp) {
loginAttempts.merge(clientIp, 1, Integer::sum);
}
private void clearAttempts(String clientIp) {
loginAttempts.remove(clientIp);
}
private String getClientIp() {
// Реализация получения IP
return "127.0.0.1"; // Placeholder
}
// С логированием и мониторингом
public boolean loginWithMonitoring(String username, String password) {
long startTime = System.currentTimeMillis();
String clientIp = getClientIp();
try {
boolean success = loginSafe(username, password);
// Логирование попытки входа
logLoginAttempt(username, clientIp, success,
System.currentTimeMillis() - startTime);
return success;
} catch (Exception e) {
logSecurityEvent("Login-Fehler", username, clientIp, e.getMessage());
return false;
}
}
private void logLoginAttempt(String username, String clientIp,
boolean success, long duration) {
String logLevel = success ? "INFO" : "WARN";
System.out.printf("[%s] Login %s: user=%s, ip=%s, duration=%dms%n",
logLevel, success ? "erfolgreich" : "fehlgeschlagen",
username, clientIp, duration);
}
private void logSecurityEvent(String event, String username,
String clientIp, String details) {
System.out.printf("[SECURITY] %s: user=%s, ip=%s, details=%s%n",
event, username, clientIp, details);
}
}
// Демонстрация SQL Injection
public class SQLInjectionDemo {
public static void main(String[] args) {
System.out.println("=== SQL Injection Demo ===");
try {
// Подключение к H2 in-memory БД
Connection conn = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "");
// Создание тестовой БД
setupTestDatabase(conn);
// Создание сервисов
UnsafeLoginService unsafeService = new UnsafeLoginService(conn);
SafeLoginService safeService = new SafeLoginService(conn);
AdvancedSecurityService advancedService = new AdvancedSecurityService(conn);
// Обычные попытки входа
System.out.println("\n--- Обычные попытки входа ---");
testLogin(unsafeService, "admin", "password123", "Unsicher");
testLogin(safeService, "admin", "password123", "Sicher");
// Атаки SQL Injection
System.out.println("\n--- Атаки SQL Injection ---");
// Классическая атака injection
String injectionUsername = "admin' OR '1'='1";
String injectionPassword = "anything";
System.out.println("Injection-Username: " + injectionUsername);
System.out.println("Injection-Password: " + injectionPassword);
boolean unsafeResult = unsafeService.loginUnsafe(injectionUsername, injectionPassword);
System.out.println("Unsicherer Login (Injection): " +
(unsafeResult ? "ERFOLGREICH (Gefahr!)" : "fehlgeschlagen"));
boolean safeResult = safeService.loginSafe(injectionUsername, injectionPassword);
System.out.println("Sicherer Login (Injection): " +
(safeResult ? "erfolgreich" : "fehlgeschlagen"));
// Расширенные меры защиты
System.out.println("\n--- Расширенные меры защиты ---");
// С валидацией входа
boolean validationResult = safeService.loginWithValidation("admin", "password123");
System.out.println("Login с валидацией: " +
(validationResult ? "erfolgreich" : "fehlgeschlagen"));
boolean invalidResult = safeService.loginWithValidation("admin'; DROP TABLE users; --", "password");
System.out.println("Login с невалидными данными: " +
(invalidResult ? "erfolgreich" : "fehlgeschlagen"));
// С Rate Limiting
boolean rateLimitedResult = advancedService.loginWithRateLimit("admin", "password123");
System.out.println("Login с Rate Limiting: " +
(rateLimitedResult ? "erfolgreich" : "fehlgeschlagen"));
// С мониторингом
boolean monitoredResult = advancedService.loginWithMonitoring("admin", "password123");
System.out.println("Login с мониторингом: " +
(monitoredResult ? "erfolgreich" : "fehlgeschlagen"));
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void testLogin(Object service, String username, String password, String type) {
try {
boolean result;
if (service instanceof UnsafeLoginService) {
result = ((UnsafeLoginService) service).loginUnsafe(username, password);
} else if (service instanceof SafeLoginService) {
result = ((SafeLoginService) service).loginSafe(username, password);
} else {
result = false;
}
System.out.printf("%s Login (%s, %s): %s%n",
type, username, password,
result ? "erfolgreich" : "fehlgeschlagen");
} catch (Exception e) {
System.out.printf("%s Login: Fehler - %s%n", type, e.getMessage());
}
}
private static void setupTestDatabase(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
// Создание таблицы users
stmt.execute("DROP TABLE users IF EXISTS");
stmt.execute("""
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""");
// Добавление тестовых данных
stmt.execute("INSERT INTO users (username, password, email) VALUES " +
"('admin', 'password123', 'admin@example.com'), " +
"('user1', 'user123', 'user1@example.com'), " +
"('user2', 'user456', 'user2@example.com')");
System.out.println("Test-Datenbank erstellt");
}
}
}
2. Защита в ORM фреймворках
import javax.persistence.*;
import java.util.List;
import java.util.regex.Pattern;
// JPA Entity
@Entity
@Table(name = "users")
class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
private String email;
// Getter и Setter
public Long getId() { return id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
// Безопасный Service с JPA
class SecureUserService {
private EntityManager em;
public SecureUserService(EntityManager em) {
this.em = em;
}
// Безопасная проверка входа через JPA
public User authenticate(String username, String password) {
// Валидация входных данных
if (!isValidInput(username) || !isValidInput(password)) {
throw new IllegalArgumentException("Ungültige Eingabe");
}
try {
// JPA Query с именованными параметрами (защита встроена)
TypedQuery<User> query = em.createQuery(
"SELECT u FROM User u WHERE u.username = :username AND u.password = :password",
User.class);
query.setParameter("username", username);
query.setParameter("password", password);
List<User> results = query.getResultList();
return results.isEmpty() ? null : results.get(0);
} catch (Exception e) {
// Безопасная обработка ошибок
throw new RuntimeException("Authentifizierung fehlgeschlagen", e);
}
}
// Безопасный поиск через Criteria API
public List<User> searchUsers(String searchTerm) {
if (!isValidSearchTerm(searchTerm)) {
throw new IllegalArgumentException("Ungültiger Suchbegriff");
}
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);
// Безопасный поиск через Criteria API (без конкатенации строк)
Predicate searchPredicate = cb.or(
cb.like(user.get("username"), "%" + searchTerm + "%"),
cb.like(user.get("email"), "%" + searchTerm + "%")
);
cq.where(searchPredicate);
return em.createQuery(cq).getResultList();
}
// Безопасная пагинация
public List<User> getUsersPaginated(int page, int size) {
if (page < 0 || size <= 0 || size > 100) {
throw new IllegalArgumentException("Ungültige Paginierungsparameter");
}
TypedQuery<User> query = em.createQuery("SELECT u FROM User u ORDER BY u.username", User.class);
query.setFirstResult(page * size);
query.setMaxResults(size);
return query.getResultList();
}
private boolean isValidInput(String input) {
// Валидация по whitelist
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_@.-]{3,100}$");
return input != null && pattern.matcher(input).matches();
}
private boolean isValidSearchTerm(String term) {
// Поисковые термины допускают больше символов
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_@.-\\s]{1,50}$");
return term != null && pattern.matcher(term).matches();
}
}
// Расширенная защита со Spring Data JPA
@Repository
interface UserRepository extends JpaRepository<User, Long> {
// Безопасные запросы через методы
Optional<User> findByUsernameAndPassword(String username, String password);
@Query("SELECT u FROM User u WHERE u.username LIKE %:search% OR u.email LIKE %:search%")
List<User> findByUsernameOrEmailContaining(@Param("search") String search);
// Native Query с привязкой параметров
@Query(value = "SELECT * FROM users WHERE email = :email", nativeQuery = true)
Optional<User> findByEmailNative(@Param("email") String email);
}
// Service со Spring Data JPA
@Service
@Transactional
class UserServiceWithSpring {
private UserRepository userRepository;
public UserServiceWithSpring(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Optional<User> authenticate(String username, String password) {
// Валидация входных данных
validateInput(username, "username");
validateInput(password, "password");
// Spring Data JPA защищает автоматически
return userRepository.findByUsernameAndPassword(username, password);
}
public List<User> searchUsers(String searchTerm) {
validateSearchTerm(searchTerm);
return userRepository.findByUsernameOrEmailContaining(searchTerm);
}
private void validateInput(String input, String fieldName) {
if (input == null || input.trim().isEmpty()) {
throw new IllegalArgumentException(fieldName + " darf nicht leer sein");
}
if (input.length() > 100) {
throw new IllegalArgumentException(fieldName + " ist zu lang");
}
// Weitere Validierungsregeln...
}
private void validateSearchTerm(String term) {
if (term == null || term.trim().isEmpty()) {
throw new IllegalArgumentException("Suchbegriff darf nicht leer sein");
}
if (term.length() > 50) {
throw new IllegalArgumentException("Suchbegriff ist zu lang");
}
}
}
// ORM демонстрация
public class ORMSecurityDemo {
public static void main(String[] args) {
System.out.println("=== ORM Security Demo ===");
// В реальном приложении это управляется через Spring/DI
EntityManagerFactory emf = Persistence.createEntityManagerFactory("testdb");
EntityManager em = emf.createEntityManager();
try {
// Создание тестовых данных
setupTestData(em);
// Создание Service
SecureUserService service = new SecureUserService(em);
// Нормальная аутентификация
System.out.println("\n--- Нормальная аутентификация ---");
User user = service.authenticate("admin", "password123");
System.out.println("Authentifizierung: " + (user != null ? "erfolgreich" : "fehlgeschlagen"));
// Попытка Injection (будет заблокирована ORM)
System.out.println("\n--- Попытка Injection ---");
try {
User injectedUser = service.authenticate("admin' OR '1'='1", "anything");
System.out.println("Injection-Authentifizierung: " +
(injectedUser != null ? "erfolgreich (Gefahr!)" : "fehlgeschlagen"));
} catch (Exception e) {
System.out.println("Injection-Authentifizierung: fehlgeschlagen (geschützt)");
}
// Безопасный поиск
System.out.println("\n--- Безопасный поиск ---");
List<User> searchResults = service.searchUsers("admin");
System.out.println("Suchergebnisse: " + searchResults.size() + " Benutzer");
// Пагинация
System.out.println("\n--- Пагинация ---");
List<User> paginatedUsers = service.getUsersPaginated(0, 2);
System.out.println("Paginierte Ergebnisse: " + paginatedUsers.size() + " Benutzer");
} finally {
em.close();
emf.close();
}
}
private static void setupTestData(EntityManager em) {
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
// Создание тестовых пользователей
User admin = new User();
admin.setUsername("admin");
admin.setPassword("password123");
admin.setEmail("admin@example.com");
em.persist(admin);
User user1 = new User();
user1.setUsername("user1");
user1.setPassword("user123");
user1.setEmail("user1@example.com");
em.persist(user1);
tx.commit();
System.out.println("Test-Daten erstellt");
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
e.printStackTrace();
}
}
}
3. Примеры на PHP и Python
<?php
// Уязвимая реализация PHP (небезопасная)
class UnsafeLoginPHP {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function loginUnsafe($username, $password) {
// ОПАСНО - Прямая конкатенация строк
$query = "SELECT * FROM users WHERE username = '" . $username .
"' AND password = '" . $password . "'";
echo "Query: " . $query . "\n";
$stmt = $this->pdo->query($query);
return $stmt->rowCount() > 0;
}
}
// Защищённая реализация PHP
class SafeLoginPHP {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function loginSafe($username, $password) {
// БЕЗОПАСНО - Prepared Statement
$query = "SELECT * FROM users WHERE username = ? AND password = ?";
$stmt = $this->pdo->prepare($query);
$stmt->execute([$username, $password]);
return $stmt->rowCount() > 0;
}
public function loginWithValidation($username, $password) {
// Валидация входных данных
if (!$this->isValidUsername($username) || !$this->isValidPassword($password)) {
throw new InvalidArgumentException("Ungültige Eingabe");
}
return $this->loginSafe($username, $password);
}
private function isValidUsername($username) {
// Whitelist-валидация
return preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username);
}
private function isValidPassword($password) {
return strlen($password) >= 8 && strlen($password) <= 100;
}
}
// Demo PHP
echo "=== PHP SQL Injection Demo ===\n";
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
// Создание сервисов
$unsafeService = new UnsafeLoginPHP($pdo);
$safeService = new SafeLoginPHP($pdo);
// Обычные попытки входа
echo "\n--- Обычные попытки входа ---\n";
$unsafeResult = $unsafeService->loginUnsafe('admin', 'password123');
echo "Небезопасный вход: " . ($unsafeResult ? 'успешно' : 'ошибка') . "\n";
$safeResult = $safeService->loginSafe('admin', 'password123');
echo "Защищённый вход: " . ($safeResult ? 'успешно' : 'ошибка') . "\n";
// Атака SQL Injection
echo "\n--- Атака SQL Injection ---\n";
$injectionUsername = "admin' OR '1'='1";
$injectionPassword = "anything";
echo "Username для инъекции: $injectionUsername\n";
echo "Password для инъекции: $injectionPassword\n";
$unsafeInjectionResult = $unsafeService->loginUnsafe($injectionUsername, $injectionPassword);
echo "Небезопасный вход (инъекция): " .
($unsafeInjectionResult ? 'УСПЕШНО (Опасно!)' : 'ошибка') . "\n";
$safeInjectionResult = $safeService->loginSafe($injectionUsername, $injectionPassword);
echo "Защищённый вход (инъекция): " .
($safeInjectionResult ? 'успешно' : 'ошибка') . "\n";
} catch (PDOException $e) {
echo "Ошибка БД: " . $e->getMessage() . "\n";
}
?>
# Python SQLAlchemy реализация
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import re
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True, nullable=False)
password = Column(String(100), nullable=False)
email = Column(String(100))
def __repr__(self):
return f"<User(username='{self.username}')>"
class SecureUserService:
def __init__(self, session):
self.session = session
def authenticate(self, username, password):
# Валидация входных данных
if not self.is_valid_input(username) or not self.is_valid_input(password):
raise ValueError("Ungültige Eingabe")
# SQLAlchemy Query - автоматически защищена
user = self.session.query(User).filter(
User.username == username,
User.password == password
).first()
return user
def search_users(self, search_term):
if not self.is_valid_search_term(search_term):
raise ValueError("Ungültiger Suchbegriff")
# Безопасный поиск с SQLAlchemy
users = self.session.query(User).filter(
(User.username.like(f"%{search_term}%")) |
(User.email.like(f"%{search_term}%"))
).all()
return users
def is_valid_input(self, input_str):
# Whitelist-валидация
pattern = r'^[a-zA-Z0-9_@.-]{3,100}$'
return input_str is not None and re.match(pattern, input_str) is not None
def is_valid_search_term(self, term):
# Поисковые термины с расширенным набором символов
pattern = r'^[a-zA-Z0-9_@.-\s]{1,50}$'
return term is not None and re.match(pattern, term) is not None
# Demo Python
def main():
print("=== Python SQLAlchemy Security Demo ===")
# Создание engine БД
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
try:
# Создание тестовых данных
admin = User(username='admin', password='password123', email='admin@example.com')
user1 = User(username='user1', password='user123', email='user1@example.com')
session.add(admin)
session.add(user1)
session.commit()
# Создание сервиса
service = SecureUserService(session)
# Обычная аутентификация
print("\n--- Обычная аутентификация ---")
user = service.authenticate('admin', 'password123')
print(f"Аутентификация: {'успешно' if user else 'ошибка'}")
# Попытка инъекции
print("\n--- Попытка инъекции ---")
try:
injected_user = service.authenticate("admin' OR '1'='1", "anything")
print(f"Аутентификация с инъекцией: {'успешно (Опасно!)' if injected_user else 'ошибка'}")
except Exception as e:
print(f"Аутентификация с инъекцией: ошибка (защищено)")
# Безопасный поиск
print("\n--- Безопасный поиск ---")
search_results = service.search_users('admin')
print(f"Результаты поиска: {len(search_results)} пользователей")
finally:
session.close()
if __name__ == "__main__":
main()
Методы SQL Injection
Union-based Injection
-- Исходный запрос
SELECT * FROM users WHERE username = 'admin' AND password = 'password'
-- Инъекция
SELECT * FROM users WHERE username = 'admin' UNION SELECT table_name, null, null, null FROM information_schema.tables--' AND password = 'anything'
Boolean-based Injection
-- Исходный запрос
SELECT * FROM products WHERE category = 'electronics'
-- Инъекция
SELECT * FROM products WHERE category = 'electronics' AND 1=1--' -- Всегда истина
SELECT * FROM products WHERE category = 'electronics' AND 1=0--' -- Всегда ложь
Инъекция на основе временных задержек
-- Исходный запрос
SELECT * FROM users WHERE id = 1
-- Инъекция с временной задержкой
SELECT * FROM users WHERE id = 1 AND (SELECT SLEEP(5))--' -- Задержка ответа при выполнении условия
Обзор защитных мер
| Мера | Описание | Эффективность |
|---|---|---|
| Prepared Statements | Параметризованные запросы | Очень высокая |
| ORM Framework | Слой абстракции | Высокая |
| Валидация входных данных | Проверка по белому списку | Средняя |
| Least Privilege | Минимальные права на БД | Высокая |
| Обработка ошибок | Скрытие ошибок БД от клиента | Средняя |
| Rate Limiting | Ограничение количества попыток | Средняя |
Паттерны валидации входных данных
Проверка по белому списку
// Только разрешённые символы
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_]{3,20}$");
boolean isValid = pattern.matcher(input).matches();
Проверка длины
// Ограничение по длине
if (input == null || input.length() < 3 || input.length() > 100) {
throw new IllegalArgumentException("Недопустимая длина");
}
Проверка типа данных
// Проверка ожидаемых типов данных
try {
int number = Integer.parseInt(input);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Ожидается число");
}
Лучшие практики безопасности БД
Принцип минимальных привилегий
-- Пользователь веб-приложения с минимальными правами
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE ON app_db.users TO 'webapp';
GRANT SELECT ON app_db.products TO 'webapp';
-- БЕЗ прав DROP, ALTER, CREATE
Хранимые процедуры
-- Безопасная хранимая процедура
CREATE PROCEDURE authenticate_user(IN p_username VARCHAR(50), IN p_password VARCHAR(100))
BEGIN
SELECT id FROM users WHERE username = p_username AND password = p_password;
END;
-- Вызов из приложения
CALL authenticate_user(?, ?);
Мониторинг и обнаружение
Обнаружение аномалий
// Выявление подозрительных паттернов запросов
public class QueryMonitor {
private Map<String, Integer> queryPatterns = new ConcurrentHashMap<>();
public void monitorQuery(String query, String ip) {
// Анализ паттерна запроса
String pattern = extractPattern(query);
// Проверка частоты
int count = queryPatterns.merge(pattern, 1, Integer::sum);
if (count > 100) { // Порог срабатывания
alertSecurityTeam("Suspicious query pattern", ip, pattern);
}
}
private String extractPattern(String query) {
// Нормализация запроса для распознавания паттерна
return query.replaceAll("\\d+", "N")
.replaceAll("'[^']*'", "'S'")
.toLowerCase();
}
private void alertSecurityTeam(String message, String ip, String details) {
System.out.printf("[ALERT] %s from %s: %s%n", message, ip, details);
// Send to SIEM system
}
}
Преимущества и недостатки
Преимущества защитных мер
- Безопасность: предотвращение компрометации БД
- Соответствие стандартам: выполнение требований безопасности
- Доверие: защита данных пользователей
- Стабильность: предотвращение повреждения данных
Недостатки
- Производительность: небольшой оверхед валидации
- Сложность: требует дополнительного кода
- Поддержка: необходимо регулярно обновлять
- Ложные срабатывания: возможны ложные положительные результаты
Типовые вопросы на собеседованиях
-
Что такое SQL Injection и как это работает? Внедрение SQL-команд через поля ввода путём конкатенации строк в запросы.
-
Как лучше всего защититься от SQL Injection? Prepared Statements с параметризацией являются самым эффективным способом защиты.
-
Почему ORM Framework безопаснее? Они абстрагируют SQL-запросы и автоматически используют Prepared Statements.
-
В чём разница между валидацией по белому и чёрному списку? Белый список разрешает только определённые символы, чёрный список блокирует известные опасные паттерны.
Основные источники
- https://owasp.org/www-community/attacks/SQL_Injection
- https://www.w3schools.com/sql/sql_injection.asp
- https://portswigger.net/web-security/sql-injection



