Skip to content

Kapitel 16: Sicherheit und Optimierung

16.1 SQL-Injection verhindern

Was ist SQL-Injection?

SQL-Injection: Eine Sicherheitslücke, bei der Angreifer bösartigen SQL-Code einschleusen.

Anfälliges Beispiel (NIE so machen!):

php
<?php
    // GEFÄHRLICH! SQL-Injection möglich!
    $name = $_POST['name'];
    $sql = "SELECT * FROM benutzer WHERE name = '$name'";
    $result = $conn->query($sql);
    
    // Wenn Angreifer als $name eingibt:
    // ' OR '1'='1
    // Dann wird die SQL-Abfrage zu:
    // SELECT * FROM benutzer WHERE name = '' OR '1'='1'
    // Das gibt ALLE Benutzer zurück!
?>

Schutzmassnahmen gegen SQL-Injection

1. Prepared Statements verwenden (WICHTIGSTE Massnahme!)

php
<?php
    // SICHER! Prepared Statements verwenden
    $sql = "SELECT * FROM benutzer WHERE name = ?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("s", $name);
    $stmt->execute();
    $result = $stmt->get_result();
    
    // Bei INSERT/UPDATE/DELETE genauso:
    $sql = "INSERT INTO benutzer (name, email) VALUES (?, ?)";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("ss", $name, $email);
    $stmt->execute();
?>

2. Eingaben validieren und bereinigen

php
<?php
    // Eingabe validieren
    $name = trim($_POST['name'] ?? '');
    
    if (empty($name)) {
        die("Name darf nicht leer sein.");
    }
    
    if (!preg_match('/^[A-Za-zÄäÖöÜüß ]+$/', $name)) {
        die("Name enthält ungültige Zeichen.");
    }
    
    // Für numerische Werte: (int) oder intval() verwenden
    $id = (int) ($_POST['id'] ?? 0);
    
    // Für Strings: htmlspecialchars() verwenden (gegen XSS)
    $name_sicher = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
?>

3. mysqli_real_escape_string() (wenn Prepared Statements nicht möglich)

php
<?php
    // Nicht ideal, aber besser als nichts
    $name = mysqli_real_escape_string($conn, $_POST['name']);
    $sql = "SELECT * FROM benutzer WHERE name = '$name'";
    $result = $conn->query($sql);
?>

Wichtig: Prepared Statements sind IMMER vorzuziehen!

16.2 XSS verhindern (Cross-Site Scripting)

Was ist XSS?

XSS (Cross-Site Scripting): Eine Sicherheitslücke, bei der Angreifer bösartigen JavaScript-Code einschleusen.

Anfälliges Beispiel (NIE so machen!):

php
<?php
    // GEFÄHRLICH! XSS möglich!
    $name = $_GET['name'];
    echo "Willkommen " . $name;
    
    // Wenn Angreifer als $name eingibt:
    // <script>alert('XSS')</script>
    // Dann wird das JavaScript ausgeführt!
?>

Schutzmassnahmen gegen XSS

1. htmlspecialchars() verwenden (WICHTIGSTE Massnahme!)

php
<?php
    // SICHER! htmlspecialchars() verwenden
    $name = $_GET['name'] ?? '';
    $name_sicher = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
    echo "Willkommen " . $name_sicher;
    
    // Was htmlspecialchars() macht:
    // & → &amp;
    // " → &quot;
    // ' → &#039;  (mit ENT_QUOTES)
    // < → &lt;
    // > → &gt;
    
    // Also wird aus <script>alert('XSS')</script>
    // &lt;script&gt;alert('XSS')&lt;/script&gt;
    // Das wird im Browser als Text angezeigt, nicht ausgeführt!
?>

2. Für HTML-Attribute (value="" etc.)

php
<?php
    $wert = $_GET['wert'] ?? '';
    $wert_sicher = htmlspecialchars($wert, ENT_QUOTES, 'UTF-8');
    echo '<input type="text" value="' . $wert_sicher . '">';
?>

3. Für JSON-Ausgabe

php
<?php
    $daten = ["name" => $name];
    echo json_encode($daten, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
?>

4. Content Security Policy (CSP) Header setzen

php
<?php
    // CSP Header setzen (schützt vor XSS)
    header("Content-Security-Policy: default-src 'self'; script-src 'self'");
?>

Weitere XSS-Schutzmassnahmen:

  1. Eingabedaten validieren (Server + Client)
  2. Template-Systeme verwenden (die automatisch escapen)
  3. Keine Benutzereingaben direkt in eval() oder exec() verwenden
  4. strip_tags() verwenden (um HTML-Tags zu entfernen - aber Vorsicht!)
php
<?php
    // HTML-Tags entfernen (aber nicht ideal für XSS-Schutz!)
    $text = "<script>alert('XSS')</script><b>Hallo</b>";
    $text_ohne_tags = strip_tags($text);
    echo $text_ohne_tags;  // Hallo
?>

16.3 Datei-Upload-Lücken verhindern

Gefahren beim Datei-Upload:

  1. Code-Ausführung: Angreifer lädt PHP-Datei hoch und führt sie aus
  2. Datei-Überschreibung: Angreifer überschreibt wichtige Dateien
  3. Speicherplatz-Erschöpfung: Angreifer lädt riesige Dateien hoch
  4. Dateiname-Konflikt: Angreifer verwendet gleichen Dateinamen wie existierende Datei

Schutzmassnahmen gegen Datei-Upload-Lücken

1. Dateinamen ändern (Sicherheit!)

php
<?php
    // Dateinamen ändern (um Überschreibungen und Code-Ausführung zu verhindern)
    $dateiname = $_FILES['datei']['name'];
    $dateiendung = strtolower(pathinfo($dateiname, PATHINFO_EXTENSION));
    
    // Neuen Dateinamen generieren
    $neuer_dateiname = uniqid() . '_' . time() . '.' . $dateiendung;
?>

2. Dateityp prüfen (nicht nur auf Dateiendung vertrauen!)

php
<?php
    // Dateityp prüfen (MIME-Type)
    $erlaubte_typen = ['image/jpeg', 'image/png', 'image/gif'];
    $datei_typ = $_FILES['datei']['type'];
    
    if (!in_array($datei_typ, $erlaubte_typen)) {
        die("Nur JPG, PNG und GIF sind erlaubt!");
    }
    
    // Auch Dateiendung prüfen
    $erlaubte_endungen = ['jpg', 'jpeg', 'png', 'gif'];
    $dateiendung = strtolower(pathinfo($_FILES['datei']['name'], PATHINFO_EXTENSION));
    
    if (!in_array($dateiendung, $erlaubte_endungen)) {
        die("Ungültige Dateiendung!");
    }
?>

3. Dateigröße begrenzen

php
<?php
    // Dateigröße prüfen (max. 5MB)
    $max_groesse = 5 * 1024 * 1024;  // 5MB in Bytes
    $datei_groesse = $_FILES['datei']['size'];
    
    if ($datei_groesse > $max_groesse) {
        die("Datei ist zu groß! Maximale Größe: 5MB");
    }
?>

4. Upload-Verzeichnis auserhalb des Webroots (wenn möglich)

php
<?php
    // Upload-Verzeichnis auserhalb des Webroots (sicherer)
    $ziel_verzeichnis = '../uploads/';  // Ausserhalb von public_html
    
    // Oder: .htaccess im Upload-Verzeichnis verwenden
    // Deny from all
    // <FilesMatch "\.(jpg|jpeg|png|gif)$">
    //     Allow from all
    // </FilesMatch>
?>

5. .htaccess im Upload-Verzeichnis (um Ausführung von PHP-Dateien zu verhindern)

apache
# .htaccess im Upload-Verzeichnis
deny from all

<FilesMatch "\.(jpg|jpeg|png|gif)$">
    Allow from all
</FilesMatch>

16.4 Code-Standards und Kommentare

PHP Code-Standards (PSR-12)

PSR (PHP Standard Recommendations): Standardisierte Kodierungsstandards für PHP.

Wichtigste PSR-Standards:

PSRBeschreibung
PSR-1Grundlegende Kodierungsstandards
PSR-12Kodierungsstandards (Ersetzt PSR-2)
PSR-4Autoloading-Standards
PSR-3Logger-Interface

PSR-12 Grundregeln:

1. Dateien

php
<?php
    // Datei muss mit <?php beginnen (ohne schließendes ?> am Ende!)
    // Datei muss UTF-8 kodiert sein (ohne BOM)
    
    declare(strict_types=1);  // Strenge Typüberprüfung
    
    // Namespace und Use-Deklarationen
    namespace MeineApp\Controllers;
    
    use MeineApp\Models\Benutzer;
    use MeineApp\Services\EmailService;
    
    // Eine Klasse pro Datei
    class MeineController
    {
        // ...
    }
?>

2. Klassen, Methoden, Eigenschaften

php
<?php
    // Klasse: Grossschreibung (PascalCase)
    class MeineController
    {
        // Konstante: Grossschreibung
        public const VERSION = '1.0.0';
        
        // Eigenschaft: camelCase (für öffentliche/geschützte) oder snake_case (für private)
        public $meineEigenschaft;
        private $meine_private_eigenschaft;
        
        // Konstruktor
        public function __construct(
            private EmailService $emailService,
            private Benutzer $benutzer
        ) {
            // ...
        }
        
        // Methode: camelCase
        public function meineMethode(): string
        {
            // ...
        }
    }
?>

3. Kontrollstrukturen

php
<?php
    // if/else/elseif
    if ($a === $b) {
        // ...
    } elseif ($a === $c) {
        // ...
    } else {
        // ...
    }
    
    // switch
    switch ($wert) {
        case 1:
            // ...
            break;
            
        case 2:
            // ...
            break;
            
        default:
            // ...
            break;
    }
    
    // while/do-while/for/foreach
    while ($i < 10) {
        // ...
    }
    
    foreach ($array as $wert) {
        // ...
    }
?>

Kommentare (PHPDoc)

php
<?php
    /**
     * Beschreibung der Klasse.
     *
     * @package MeineApp\Controllers
     */
    class MeineController
    {
        /**
         * Beschreibung der Methode.
         *
         * @param string $name Der Name.
         * @param int $alter Das Alter.
         * @return string Die Begrüßung.
         * @throws \InvalidArgumentException Wenn Name leer ist.
         */
        public function begruesse(string $name, int $alter): string
        {
            if (empty($name)) {
                throw new \InvalidArgumentException('Name darf nicht leer sein.');
            }
            
            return "Hallo " . $name . " (" . $alter . " Jahre alt)";
        }
    }
?>

16.5 Fehlerbehandlung und Logging

Fehlerberichterstattung (Error Reporting)

php
<?php
    // In Entwicklungsumgebung: Alle Fehler anzeigen
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    
    // In Produktionsumgebung: Fehler nicht anzeigen (nur loggen)
    error_reporting(E_ALL);
    ini_set('display_errors', 0);
    ini_set('log_errors', 1);
    ini_set('error_log', '/pfad/zu/error.log');
?>

Try-Catch (Ausnahmebehandlung)

php
<?php
    // Try-Catch-Block
    try {
        // Code, der eine Ausnahme auslösen könnte
        $datei = fopen('nicht_existierende_datei.txt', 'r');
        
        if (!$datei) {
            throw new \Exception('Datei konnte nicht geöffnet werden.');
        }
        
        // ...
        
    } catch (\IOException $e) {
        // Spezifische Ausnahmebehandlung
        error_log("I/O-Fehler: " . $e->getMessage());
        echo "Ein I/O-Fehler ist aufgetreten.";
        
    } catch (\Exception $e) {
        // Allgemeine Ausnahmebehandlung
        error_log("Fehler: " . $e->getMessage());
        echo "Ein Fehler ist aufgetreten.";
        
    } finally {
        // Wird immer ausgeführt (auch bei Ausnahme)
        if (isset($datei) && $datei) {
            fclose($datei);
        }
    }
?>

Eigene Ausnahmen (Custom Exceptions)

php
<?php
    // Eigene Ausnahme-Klasse
    class MeineAusnahme extends \Exception
    {
        public function __construct(string $message = "", int $code = 0, ?\Throwable $previous = null)
        {
            parent::__construct($message, $code, $previous);
        }
    }
    
    // Verwendung
    try {
        $alter = -5;
        
        if ($alter < 0) {
            throw new MeineAusnahme('Alter darf nicht negativ sein.');
        }
        
    } catch (MeineAusnahme $e) {
        error_log("Meine Ausnahme: " . $e->getMessage());
        echo "Ein benutzerdefinierter Fehler ist aufgetreten.";
    }
?>

Logging (Fehlerprotokollierung)

php
<?php
    // Fehler in Datei loggen
    error_log("Dies ist eine Fehlermeldung.", 3, "/pfad/zu/error.log");
    
    // Fehler per E-Mail senden
    error_log("Dies ist eine Fehlermeldung.", 1, "admin@example.com");
    
    // Fehler an PHP's Fehlerprotokollierung senden
    error_log("Dies ist eine Fehlermeldung.");
    
    // Benutzerdefiniertes Logging
    function logMessage(string $message, string $level = 'INFO'): void
    {
        $datum = date('Y-m-d H:i:s');
        $logMessage = "[$datum] [$level] $message" . PHP_EOL;
        
        file_put_contents('/pfad/zu/app.log', $logMessage, FILE_APPEND);
    }
    
    // Verwendung
    logMessage("Benutzer eingeloggt: " . $benutzername);
    logMessage("Fehler beim Speichern!", "ERROR");
?>

Zusammenfassung

In diesem Kapitel haben Sie:

  • ✅ SQL-Injection mit Prepared Statements verhindert
  • ✅ XSS mit htmlspecialchars() verhindert
  • ✅ Datei-Upload-Lücken mit Validierung und Sicherheitsmassnahmen verhindert
  • ✅ PHP Code-Standards (PSR-12) gelernt
  • ✅ Fehlerbehandlung und Logging implementiert

Nächstes Kapitel: Wir werden häufig verwendete Funktionen entwickeln (E-Mail senden, Captcha, Paginierung, API-Entwicklung).


Übungsaufgaben:

  1. Erstellen Sie ein Formular mit XSS-Schutz
  2. Implementieren Sie eine sichere Datei-Upload-Funktion
  3. Schreiben Sie einen benutzerdefinierten Logger
  4. Erstellen Sie eine Klasse mit PSR-12-Standards

Häufige Fehler:

  • htmlspecialchars() ohne ENT_QUOTES verwenden → Auch einfache Anführungszeichen escapen!
  • ❌ Dateinamen nicht ändern → Sicherheitsrisiko (Code-Ausführung)!
  • display_errors = 1 in Produktion → Sicherheitsrisiko!
  • error_log() vergessen → Fehler werden nicht protokolliert!

Frei für alle Anfänger