Skip to content
IRC-Coding IRC-Coding
SQL Injection Sicherheitsrisiko Prepared Statements ORM Frameworks Input Validierung

SQL Injection: Sicherheitsrisiko, Schutzmaßnahmen & Best Practices

SQL Injection Sicherheitsrisiko mit Schutzmaßnahmen. Prepared Statements, Parameterized Queries, ORM Frameworks, Input-Validierung und sichere Programmierung.

S

schutzgeist

2 min read

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

  1. Angriffsvektoren: Mögliche Eingabepunkte für Injection
  2. Injection-Techniken: Union-based, Boolean-based, Time-based
  3. Schutzmaßnahmen: Prepared Statements, Validierung, ORM
  4. ORM Frameworks: Abstraktionsschicht für Datenbankzugriffe
  5. Input-Validierung: Whitelist-basierte Eingabeprüfung
  6. Error Handling: Sichere Fehlerbehandlung
  7. Security Headers: Additional Protection Layers
  8. 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ßnahmeBeschreibungWirksamkeit
Prepared StatementsParametrisierte AbfragenSehr hoch
ORM FrameworksAbstraktionsschichtHoch
Input-ValidierungWhitelist-basierte PrüfungMittel
Least PrivilegeMinimale DB-RechteHoch
Error HandlingKeine DB-Fehler nach außenMittel
Rate LimitingBegrenzung von VersuchenMittel

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

  1. Was ist SQL Injection und wie funktioniert es? Einschleusen von SQL-Befehlen über Eingabefelder durch String-Konkatenation in Queries.

  2. Wie schützt man sich am besten gegen SQL Injection? Prepared Statements mit Parametrisierung sind die effektivste Schutzmaßnahme.

  3. Warum sind ORM Frameworks sicherer? Sie abstrahieren SQL-Queries und verwenden automatisch Prepared Statements.

  4. Was ist der Unterschied zwischen Whitelist und Blacklist Validierung? Whitelist erlaubt nur definierte Zeichen, Blacklist blockiert bekannte gefährliche Muster.

Wichtigste Quellen

  1. https://owasp.org/www-community/attacks/SQL_Injection
  2. https://www.w3schools.com/sql/sql_injection.asp
  3. https://portswigger.net/web-security/sql-injection
Zurück zum Blog
Share:

Ähnliche Beiträge