SQL Injection: Sicherheitsrisiko, Schutzmaßnahmen & Best Practices
Dieser Beitrag ist eine umfassende Anleitung zur SQL Injection – inklusive Sicherheitsrisiken, Schutzmaßnahmen, Prepared Statements, ORM Frameworks und Input-Validierung mit praktischen Beispielen.
In a Nutshell
SQL Injection ist eine kritische Sicherheitslücke bei der Angreifer manipulierte SQL-Befehle einschleusen können. Schutz durch Prepared Statements, Input-Validierung und ORM Frameworks.
Kompakte Fachbeschreibung
SQL Injection ist eine Angriffstechnik, bei der Angreifer bösartige SQL-Befehle in Eingabefelder einschleusen, um unberechtigten Zugriff auf Datenbanken zu erhalten oder Daten zu manipulieren.
Angriffsvektoren:
- Login-Formulare: Umgehung von Authentifizierung
- Suchfelder: Extraktion sensibler Daten
- URL-Parameter: Manipulation von Datenbankabfragen
- Datei-Uploads: Einschleusung über Dateinamen
- API-Endpunkte: Direkte Datenbankmanipulation
Schutzmaßnahmen:
- Prepared Statements: Parametrisierte Abfragen
- Input-Validierung: Whitelist-basierte Prüfung
- ORM Frameworks: Abstraktionsebene für Datenbankzugriffe
- Least Privilege: Minimale Datenbankrechte
- Error Handling: Keine Datenbankfehler nach außen
Prüfungsrelevante Stichpunkte
- SQL Injection: Einschleusen von SQL-Befehlen über Eingabefelder
- Angriffsziele: Datenextraktion, -manipulation, -zerstörung
- Schwachstellen: Login-Formulare, Suchfelder, URL-Parameter
- Schutzmaßnahmen: Prepared Statements, Input-Validierung, ORM
- Prepared Statements: Parametrisierte Abfragen mit Platzhaltern
- ORM Frameworks: Hibernate, Entity Framework, SQLAlchemy
- Least Privilege: Minimale Rechte für Datenbankzugriffe
- IHK-relevant: Kritisches Sicherheitsrisiko in Webanwendungen
Kernkomponenten
- Angriffsvektoren: Mögliche Eingabepunkte für Injection
- Injection-Techniken: Union-based, Boolean-based, Time-based
- Schutzmaßnahmen: Prepared Statements, Validierung, ORM
- ORM Frameworks: Abstraktionsschicht für Datenbankzugriffe
- Input-Validierung: Whitelist-basierte Eingabeprüfung
- Error Handling: Sichere Fehlerbehandlung
- Security Headers: Additional Protection Layers
- Monitoring: Erkennung von Angriffsversuchen
Praxisbeispiele
1. SQL Injection Angriffe und Gegenmaßnahmen
import java.sql.*;
import java.util.Scanner;
import java.util.regex.Pattern;
// Unsichere Implementierung (verwundbar für SQL Injection)
class UnsafeLoginService {
private Connection connection;
public UnsafeLoginService(Connection connection) {
this.connection = connection;
}
public boolean loginUnsafe(String username, String password) {
// SEHR GEFÄHRLICH - Direkte String-Konkatenation
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(); // Login erfolgreich wenn Ergebnis vorhanden
} catch (SQLException e) {
System.err.println("Datenbankfehler: " + e.getMessage());
return false;
}
}
}
// Sichere Implementierung mit Prepared Statements
class SafeLoginService {
private Connection connection;
public SafeLoginService(Connection connection) {
this.connection = connection;
}
public boolean loginSafe(String username, String password) {
// SICHER - Prepared Statement mit Platzhaltern
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
try (PreparedStatement pstmt = connection.prepareStatement(query)) {
// Parameter setzen (automatisch escaped)
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;
}
}
// Mit Input-Validierung
public boolean loginWithValidation(String username, String password) {
// Input-Validierung vor der Datenbankabfrage
if (!isValidUsername(username) || !isValidPassword(password)) {
System.err.println("Ungültige Eingabe");
return false;
}
return loginSafe(username, password);
}
private boolean isValidUsername(String username) {
// Whitelist: Nur alphanumerische Zeichen und Unterstrich
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_]{3,20}$");
return pattern.matcher(username).matches();
}
private boolean isValidPassword(String password) {
// Mindestens 8 Zeichen, max 100 Zeichen
return password != null &&
password.length() >= 8 &&
password.length() <= 100;
}
}
// Erweiterte Schutzmaßnahmen
class AdvancedSecurityService {
private Connection connection;
public AdvancedSecurityService(Connection connection) {
this.connection = connection;
}
// Mit 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 Minuten
public boolean loginWithRateLimit(String username, String password) {
String clientIp = getClientIp(); // Implementierung erforderlich
// Rate Limiting prüfen
if (isBlocked(clientIp)) {
System.err.println("IP geblockt wegen zu vieler Versuche");
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() {
// Implementierung zur IP-Extraktion
return "127.0.0.1"; // Platzhalter
}
// Mit Logging und Monitoring
public boolean loginWithMonitoring(String username, String password) {
long startTime = System.currentTimeMillis();
String clientIp = getClientIp();
try {
boolean success = loginSafe(username, password);
// Loggen des Login-Versuchs
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 Demo
public class SQLInjectionDemo {
public static void main(String[] args) {
System.out.println("=== SQL Injection Demo ===");
try {
// Verbindung zur H2-In-Memory-Datenbank
Connection conn = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "");
// Test-Tabelle erstellen
setupTestDatabase(conn);
// Services erstellen
UnsafeLoginService unsafeService = new UnsafeLoginService(conn);
SafeLoginService safeService = new SafeLoginService(conn);
AdvancedSecurityService advancedService = new AdvancedSecurityService(conn);
// Normale Login-Versuche
System.out.println("\n--- Normale Login-Versuche ---");
testLogin(unsafeService, "admin", "password123", "Unsicher");
testLogin(safeService, "admin", "password123", "Sicher");
// SQL Injection Angriffe
System.out.println("\n--- SQL Injection Angriffe ---");
// Klassischer Injection-Angriff
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"));
// Erweiterte Schutzmaßnahmen
System.out.println("\n--- Erweiterte Schutzmaßnahmen ---");
// Mit Input-Validierung
boolean validationResult = safeService.loginWithValidation("admin", "password123");
System.out.println("Login mit Validierung: " +
(validationResult ? "erfolgreich" : "fehlgeschlagen"));
boolean invalidResult = safeService.loginWithValidation("admin'; DROP TABLE users; --", "password");
System.out.println("Login mit ungültiger Eingabe: " +
(invalidResult ? "erfolgreich" : "fehlgeschlagen"));
// Mit Rate Limiting
boolean rateLimitedResult = advancedService.loginWithRateLimit("admin", "password123");
System.out.println("Login mit Rate Limiting: " +
(rateLimitedResult ? "erfolgreich" : "fehlgeschlagen"));
// Mit Monitoring
boolean monitoredResult = advancedService.loginWithMonitoring("admin", "password123");
System.out.println("Login mit Monitoring: " +
(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-Tabelle erstellen
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
)
""");
// Test-Daten einfügen
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 Framework Schutzmaßnahmen
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 und 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; }
}
// Sichere Service-Klasse mit JPA
class SecureUserService {
private EntityManager em;
public SecureUserService(EntityManager em) {
this.em = em;
}
// Sichere Login-Prüfung mit JPA
public User authenticate(String username, String password) {
// Input-Validierung
if (!isValidInput(username) || !isValidInput(password)) {
throw new IllegalArgumentException("Ungültige Eingabe");
}
try {
// JPA Query mit Named Parameters (automatisch sicher)
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) {
// Sichere Fehlerbehandlung
throw new RuntimeException("Authentifizierung fehlgeschlagen", e);
}
}
// Sichere Suche mit JPA 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);
// Sichere Suche mit Criteria API (keine String-Konkatenation)
Predicate searchPredicate = cb.or(
cb.like(user.get("username"), "%" + searchTerm + "%"),
cb.like(user.get("email"), "%" + searchTerm + "%")
);
cq.where(searchPredicate);
return em.createQuery(cq).getResultList();
}
// Sichere Paginierung
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-Validierung
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_@.-]{3,100}$");
return input != null && pattern.matcher(input).matches();
}
private boolean isValidSearchTerm(String term) {
// Suchbegriffe erlauben mehr Zeichen
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_@.-\\s]{1,50}$");
return term != null && pattern.matcher(term).matches();
}
}
// Erweiterte Schutzmaßnahmen mit Spring Data JPA
@Repository
interface UserRepository extends JpaRepository<User, Long> {
// Sichere Abfragen mit Methoden-Namen
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 mit Parameter-Binding
@Query(value = "SELECT * FROM users WHERE email = :email", nativeQuery = true)
Optional<User> findByEmailNative(@Param("email") String email);
}
// Service mit 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) {
// Input-Validierung
validateInput(username, "username");
validateInput(password, "password");
// Spring Data JPA - automatisch sicher
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 Demo
public class ORMSecurityDemo {
public static void main(String[] args) {
System.out.println("=== ORM Security Demo ===");
// In einer realen Anwendung würde dies durch Spring/DI verwaltet
EntityManagerFactory emf = Persistence.createEntityManagerFactory("testdb");
EntityManager em = emf.createEntityManager();
try {
// Test-Daten erstellen
setupTestData(em);
// Service erstellen
SecureUserService service = new SecureUserService(em);
// Normale Authentifizierung
System.out.println("\n--- Normale Authentifizierung ---");
User user = service.authenticate("admin", "password123");
System.out.println("Authentifizierung: " + (user != null ? "erfolgreich" : "fehlgeschlagen"));
// Injection-Versuch (wird durch ORM verhindert)
System.out.println("\n--- Injection-Versuch ---");
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)");
}
// Sichere Suche
System.out.println("\n--- Sichere Suche ---");
List<User> searchResults = service.searchUsers("admin");
System.out.println("Suchergebnisse: " + searchResults.size() + " Benutzer");
// Paginierung
System.out.println("\n--- Paginierung ---");
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();
// Test-Benutzer erstellen
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 und Python Beispiele
<?php
// Unsichere PHP Implementierung (verwundbar)
class UnsafeLoginPHP {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function loginUnsafe($username, $password) {
// GEFÄHRLICH - Direkte String-Konkatenation
$query = "SELECT * FROM users WHERE username = '" . $username .
"' AND password = '" . $password . "'";
echo "Query: " . $query . "\n";
$stmt = $this->pdo->query($query);
return $stmt->rowCount() > 0;
}
}
// Sichere PHP Implementierung
class SafeLoginPHP {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function loginSafe($username, $password) {
// SICHER - 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) {
// Input-Validierung
if (!$this->isValidUsername($username) || !$this->isValidPassword($password)) {
throw new InvalidArgumentException("Ungültige Eingabe");
}
return $this->loginSafe($username, $password);
}
private function isValidUsername($username) {
// Whitelist-Validierung
return preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username);
}
private function isValidPassword($password) {
return strlen($password) >= 8 && strlen($password) <= 100;
}
}
// PHP Demo
echo "=== PHP SQL Injection Demo ===\n";
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
// Services erstellen
$unsafeService = new UnsafeLoginPHP($pdo);
$safeService = new SafeLoginPHP($pdo);
// Normale Login-Versuche
echo "\n--- Normale Login-Versuche ---\n";
$unsafeResult = $unsafeService->loginUnsafe('admin', 'password123');
echo "Unsicherer Login: " . ($unsafeResult ? 'erfolgreich' : 'fehlgeschlagen') . "\n";
$safeResult = $safeService->loginSafe('admin', 'password123');
echo "Sicherer Login: " . ($safeResult ? 'erfolgreich' : 'fehlgeschlagen') . "\n";
// Injection-Angriff
echo "\n--- SQL Injection Angriff ---\n";
$injectionUsername = "admin' OR '1'='1";
$injectionPassword = "anything";
echo "Injection-Username: $injectionUsername\n";
echo "Injection-Password: $injectionPassword\n";
$unsafeInjectionResult = $unsafeService->loginUnsafe($injectionUsername, $injectionPassword);
echo "Unsicherer Login (Injection): " .
($unsafeInjectionResult ? 'ERFOLGREICH (Gefahr!)' : 'fehlgeschlagen') . "\n";
$safeInjectionResult = $safeService->loginSafe($injectionUsername, $injectionPassword);
echo "Sicherer Login (Injection): " .
($safeInjectionResult ? 'erfolgreich' : 'fehlgeschlagen') . "\n";
} catch (PDOException $e) {
echo "Datenbankfehler: " . $e->getMessage() . "\n";
}
?>
# Python SQLAlchemy Implementation
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):
# Input-Validierung
if not self.is_valid_input(username) or not self.is_valid_input(password):
raise ValueError("Ungültige Eingabe")
# SQLAlchemy Query - automatisch sicher
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")
# Sichere Suche mit 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-Validierung
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):
# Suchbegriffe erlauben mehr Zeichen
pattern = r'^[a-zA-Z0-9_@.-\s]{1,50}$'
return term is not None and re.match(pattern, term) is not None
# Python Demo
def main():
print("=== Python SQLAlchemy Security Demo ===")
# Datenbank-Engine erstellen
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
try:
# Test-Daten erstellen
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 erstellen
service = SecureUserService(session)
# Normale Authentifizierung
print("\n--- Normale Authentifizierung ---")
user = service.authenticate('admin', 'password123')
print(f"Authentifizierung: {'erfolgreich' if user else 'fehlgeschlagen'}")
# Injection-Versuch
print("\n--- Injection-Versuch ---")
try:
injected_user = service.authenticate("admin' OR '1'='1", "anything")
print(f"Injection-Authentifizierung: {'erfolgreich (Gefahr!)' if injected_user else 'fehlgeschlagen'}")
except Exception as e:
print(f"Injection-Authentifizierung: fehlgeschlagen (geschützt)")
# Sichere Suche
print("\n--- Sichere Suche ---")
search_results = service.search_users('admin')
print(f"Suchergebnisse: {len(search_results)} Benutzer")
finally:
session.close()
if __name__ == "__main__":
main()
SQL Injection Techniken
Union-based Injection
-- Original Query
SELECT * FROM users WHERE username = 'admin' AND password = 'password'
-- Injection
SELECT * FROM users WHERE username = 'admin' UNION SELECT table_name, null, null, null FROM information_schema.tables--' AND password = 'anything'
Boolean-based Injection
-- Original Query
SELECT * FROM products WHERE category = 'electronics'
-- Injection
SELECT * FROM products WHERE category = 'electronics' AND 1=1--' -- Immer wahr
SELECT * FROM products WHERE category = 'electronics' AND 1=0--' -- Immer falsch
Time-based Injection
-- Original Query
SELECT * FROM users WHERE id = 1
-- Injection mit Zeitverzögerung
SELECT * FROM users WHERE id = 1 AND (SELECT SLEEP(5))--' -- Verzögert Antwort wenn wahr
Schutzmaßnahmen Übersicht
| Maßnahme | Beschreibung | Wirksamkeit |
|---|---|---|
| Prepared Statements | Parametrisierte Abfragen | Sehr hoch |
| ORM Frameworks | Abstraktionsschicht | Hoch |
| Input-Validierung | Whitelist-basierte Prüfung | Mittel |
| Least Privilege | Minimale DB-Rechte | Hoch |
| Error Handling | Keine DB-Fehler nach außen | Mittel |
| Rate Limiting | Begrenzung von Versuchen | Mittel |
Input-Validierung Patterns
Whitelist-Validierung
// Nur erlaubte Zeichen
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_]{3,20}$");
boolean isValid = pattern.matcher(input).matches();
Length-Validierung
// Längenbegrenzung
if (input == null || input.length() < 3 || input.length() > 100) {
throw new IllegalArgumentException("Ungültige Länge");
}
Content-Type-Validierung
// Erwartete Datentypen prüfen
try {
int number = Integer.parseInt(input);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Zahl erwartet");
}
Database Security Best Practices
Least Privilege Principle
-- Web-Anwendung Benutzer mit minimalen Rechten
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';
-- KEINE DROP, ALTER, CREATE Rechte
Stored Procedures
-- Sichere Stored Procedure
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;
-- Aufruf aus Anwendung
CALL authenticate_user(?, ?);
Monitoring und Detection
Anomaly Detection
// Ungewöhnliche Query-Muster erkennen
public class QueryMonitor {
private Map<String, Integer> queryPatterns = new ConcurrentHashMap<>();
public void monitorQuery(String query, String ip) {
// Query-Muster analysieren
String pattern = extractPattern(query);
// Häufigkeit prüfen
int count = queryPatterns.merge(pattern, 1, Integer::sum);
if (count > 100) { // Schwellenwert
alertSecurityTeam("Suspicious query pattern", ip, pattern);
}
}
private String extractPattern(String query) {
// Normalisiere Query für Pattern-Erkennung
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
}
}
Vorteile und Nachteile
Vorteile von Schutzmaßnahmen
- Sicherheit: Verhindert Datenbankkompromittierung
- Compliance: Erfüllt Sicherheitsstandards
- Vertrauen: Schützt Benutzerdaten
- Stabilität: Verhindert Datenbeschädigung
Nachteile
- Performance: Leichte Overhead durch Validierung
- Komplexität: Zusätzlicher Code erforderlich
- Wartung: Regelmäßige Updates notwendig
- Fehlerrate: False Positives möglich
Häufige Prüfungsfragen
-
Was ist SQL Injection und wie funktioniert es? Einschleusen von SQL-Befehlen über Eingabefelder durch String-Konkatenation in Queries.
-
Wie schützt man sich am besten gegen SQL Injection? Prepared Statements mit Parametrisierung sind die effektivste Schutzmaßnahme.
-
Warum sind ORM Frameworks sicherer? Sie abstrahieren SQL-Queries und verwenden automatisch Prepared Statements.
-
Was ist der Unterschied zwischen Whitelist und Blacklist Validierung? Whitelist erlaubt nur definierte Zeichen, Blacklist blockiert bekannte gefährliche Muster.
Wichtigste Quellen
- https://owasp.org/www-community/attacks/SQL_Injection
- https://www.w3schools.com/sql/sql_injection.asp
- https://portswigger.net/web-security/sql-injection