Appearance
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:
// & → &
// " → "
// ' → ' (mit ENT_QUOTES)
// < → <
// > → >
// Also wird aus <script>alert('XSS')</script>
// <script>alert('XSS')</script>
// 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:
- Eingabedaten validieren (Server + Client)
- Template-Systeme verwenden (die automatisch escapen)
- Keine Benutzereingaben direkt in eval() oder exec() verwenden
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:
- Code-Ausführung: Angreifer lädt PHP-Datei hoch und führt sie aus
- Datei-Überschreibung: Angreifer überschreibt wichtige Dateien
- Speicherplatz-Erschöpfung: Angreifer lädt riesige Dateien hoch
- 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:
| PSR | Beschreibung |
|---|---|
| PSR-1 | Grundlegende Kodierungsstandards |
| PSR-12 | Kodierungsstandards (Ersetzt PSR-2) |
| PSR-4 | Autoloading-Standards |
| PSR-3 | Logger-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:
- Erstellen Sie ein Formular mit XSS-Schutz
- Implementieren Sie eine sichere Datei-Upload-Funktion
- Schreiben Sie einen benutzerdefinierten Logger
- Erstellen Sie eine Klasse mit PSR-12-Standards
Häufige Fehler:
- ❌
htmlspecialchars()ohneENT_QUOTESverwenden → Auch einfache Anführungszeichen escapen! - ❌ Dateinamen nicht ändern → Sicherheitsrisiko (Code-Ausführung)!
- ❌
display_errors = 1in Produktion → Sicherheitsrisiko! - ❌
error_log()vergessen → Fehler werden nicht protokolliert!
