Kurz gesagt: Local File Inclusion in PHP entsteht, wenn include, require oder Dateifunktionen einen Pfad verwenden, den der Besucher beeinflussen kann. Die sichere Lösung ist eine feste Erlaubnisliste: Der Parameter wählt nur aus bekannten Werten, der Pfad selbst kommt nie aus der Anfrage. Serverseitig ergänzen allow_url_include=Off, open_basedir und restriktive Dateirechte den Schutz.
Was ist Local File Inclusion in PHP?
Viele ältere PHP-Anwendungen laden Seiten nach einem einfachen Muster: Ein Parameter in der Adresse bestimmt, welche Datei eingebunden wird. Wird dieser Parameter ungeprüft an include übergeben, kann ein Angreifer andere Dateien auf dem Server anfordern. Im harmloseren Fall liest er Konfigurationsdateien mit Zugangsdaten. Im schlimmeren Fall bringt er PHP dazu, Inhalte auszuführen, die er vorher an anderer Stelle abgelegt hat, etwa in einem Upload oder einer Logdatei.
Für die Geschäftsführung ist wichtig: Diese Lücke steckt selten im CMS-Kern, sondern meist in Eigenentwicklungen, alten Erweiterungen oder schnell gebauten Zusatzseiten. Sie fällt im Alltag nicht auf, weil die Website normal funktioniert.
Bei Remote File Inclusion lädt PHP Code von einer fremden Adresse. Das setzt voraus, dass allow_url_include aktiviert ist. In aktuellen PHP-Versionen ist diese Einstellung standardmäßig aus, auf alten oder falsch konfigurierten Servern findet man sie aber noch.
Typische verwundbare Muster im Code
Gefährdet sind Stellen, an denen ein Wert aus der Anfrage, aus Cookies oder aus der Datenbank Teil eines Dateipfads wird. Häufige Beispiele sind Sprachumschalter, die eine Sprachdatei nach einem Parameter laden, Seitenauswahl über einen Parameter in der Adresse, Vorlagen- oder Theme-Auswahl und Download-Skripte für Dokumente. Ein angehängtes .php am Ende des Pfads schützt dabei nicht zuverlässig, weil Angreifer auf vorhandene PHP-Dateien ausweichen können.
Eine erste Übersicht verschaffen Sie sich mit einer Suche nach Einbindungen, die Variablen enthalten. Sie liefert auch harmlose Treffer, zeigt aber, wo genauer hingesehen werden muss:
grep -rnE --include='*.php' '(include|require)(_once)?\s*\(?\s*\$' /var/www/projekt \
| grep -v '/vendor/'
Prüfen Sie jeden Treffer darauf, woher die Variable stammt und ob ein Besucher sie beeinflussen kann.
Das sichere Muster: Erlaubnisliste statt Pfad aus der Anfrage
Der zuverlässigste Schutz ist, dass der Besucher nur einen Schlüssel wählt und die Anwendung selbst entscheidet, welche Datei dazu gehört. Der Pfad wird nie aus Eingaben zusammengesetzt.
<?php
declare(strict_types=1);
$seiten = [
'start' => __DIR__ . '/../vorlagen/start.php',
'leistungen' => __DIR__ . '/../vorlagen/leistungen.php',
'kontakt' => __DIR__ . '/../vorlagen/kontakt.php',
];
$wahl = is_string($_GET['seite'] ?? null) ? $_GET['seite'] : 'start';
if (!array_key_exists($wahl, $seiten)) {
http_response_code(404);
$wahl = 'start';
}
require $seiten[$wahl];
Dieses Muster ist kurz, leicht zu prüfen und lässt keine Spielräume. In modernen Frameworks übernimmt der Router diese Aufgabe; dort sollten Controller und Vorlagen ebenfalls nie aus freien Parametern bestimmt werden.
Wenn Dateinamen dynamisch sein müssen: realpath und Basisverzeichnis
Manchmal ist eine feste Liste nicht praktikabel, etwa bei Downloads aus einem Dokumentenordner. Dann prüfen Sie den aufgelösten Pfad gegen ein festes Basisverzeichnis und lassen nur einfache Dateinamen zu.
<?php
declare(strict_types=1);
const BASIS = '/var/www/daten/dokumente';
function sichererPfad(string $name): string
{
if (!preg_match('/^[a-z0-9_-]{1,64}\.pdf$/', $name)) {
throw new InvalidArgumentException('Ungültiger Dateiname.');
}
$basis = realpath(BASIS);
$pfad = realpath($basis . DIRECTORY_SEPARATOR . $name);
if ($basis === false || $pfad === false
|| !str_starts_with($pfad, $basis . DIRECTORY_SEPARATOR)) {
throw new RuntimeException('Datei nicht gefunden.');
}
return $pfad;
}
// Ausliefern statt einbinden: readfile() führt keinen Code aus.
$datei = sichererPfad((string) ($_GET['datei'] ?? ''));
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($datei) . '"');
readfile($datei);
Zwei Punkte sind entscheidend. Der reguläre Ausdruck erlaubt keine Verzeichnistrenner und keine Sonderzeichen. Und Dateien, die nur ausgeliefert werden sollen, werden mit readfile() gelesen, nicht mit include, damit enthaltener Code nie ausgeführt wird. str_starts_with() steht ab PHP 8.0 zur Verfügung.
Serverseitige Absicherung: php.ini, open_basedir und Rechte
Sicherer Code ist die erste Verteidigungslinie. Die Serverkonfiguration sorgt dafür, dass ein übersehener Fehler weniger Schaden anrichtet.
; php.ini oder FPM-Pool (php_admin_value / php_admin_flag)
allow_url_include = Off
allow_url_fopen = Off ; nur, wenn die Anwendung keine externen URLs lädt
open_basedir = /var/www/kunde-a/:/tmp/kunde-a/
display_errors = Off
log_errors = On
allow_url_fopen wird von manchen Anwendungen für Updates oder Schnittstellen benötigt. Testen Sie das Abschalten zuerst auf einer Testumgebung. open_basedir begrenzt PHP auf die eigenen Verzeichnisse der Website, was besonders auf Servern mit mehreren Kunden wichtig ist. Einzelheiten stehen in den Ratgebern open_basedir und Isolation pro Website und php.ini sicher konfigurieren.
Ergänzend sollten Upload- und Cache-Verzeichnisse nicht ausführbar sein und Logdateien nicht im Webroot liegen. Damit fehlen einem Angreifer die Orte, an denen er eigenen Inhalt für eine Einbindung ablegen könnte.
Woran Sie Ausnutzungsversuche in Logs erkennen
Versuche, eine File Inclusion auszunutzen, hinterlassen meist Spuren im Access-Log. Auffällig sind Parameter, die Verzeichniswechsel, absolute Systempfade, Stream-Wrapper oder vollständige fremde Adressen enthalten, besonders bei Parametern wie page, file, template oder lang. Ein einzelner Treffer ist meist automatisiertes Scannen. Wichtig ist, ob eine solche Anfrage mit Statuscode 200 und ungewöhnlicher Antwortgröße beantwortet wurde.
Wie Sie dabei systematisch vorgehen, beschreibt Webserver-Logs nach einem Angriff auswerten. Finden Sie Hinweise auf erfolgreiche Zugriffe, gehen Sie davon aus, dass Konfigurationsdateien gelesen wurden, und erneuern Sie die darin enthaltenen Zugangsdaten.
Dazu gehören vor allem Datenbankpasswörter, Schlüssel für Zahlungs- und Versanddienste, SMTP-Zugänge und die geheimen Schlüssel des CMS. Prüfen Sie außerdem, ob seit dem ersten verdächtigen Zugriff neue Dateien entstanden sind. Wer Dateien lesen konnte, hat womöglich auch versucht, eigene abzulegen. In diesem Fall geht es nicht mehr nur um eine Lücke im Code, sondern um einen Sicherheitsvorfall, der strukturiert bearbeitet werden sollte.
Korrekturen dauerhaft absichern
Nach einer Korrektur lohnt es sich, die Schutzfunktion mit automatisierten Tests abzusichern. Ein einfacher Test übergibt der Seitenauswahl unbekannte Werte, Verzeichniswechsel und leere Eingaben und erwartet in allen Fällen die Standardseite oder einen Fehler 404. Solche Tests verhindern, dass eine spätere Änderung die Lücke unbemerkt wieder öffnet.
Für Geschäftsführung und Projektverantwortliche ist eine Frage an die Agentur hilfreich: Gibt es in der Anwendung Stellen, an denen Dateien anhand von Parametern geladen werden, und wie sind sie abgesichert? Eine konkrete Antwort mit Fundstellen und Tests ist ein gutes Zeichen.
Wie hilft PHP SOS konkret?
- Wir suchen in Ihrem Code gezielt nach
include,requireund Dateifunktionen mit Eingaben aus der Anfrage und bewerten jeden Fund. - Wir setzen
allow_url_include,open_basedirund passende Rechte pro Website und prüfen, dass die Anwendung danach weiter funktioniert. - Wir werten Logs auf Ausnutzungsversuche aus und klären, ob Zugangsdaten erneuert werden müssen.
- Für eine unabhängige Prüfung bieten wir ein Sicherheitsaudit mit Penetrationstest für PHP-Anwendungen an.
Häufige Fragen
Was ist der Unterschied zwischen LFI und RFI?
Bei Local File Inclusion lädt die Anwendung eine Datei, die bereits auf dem Server liegt, aber nicht geladen werden sollte. Bei Remote File Inclusion wird Code von einer fremden Adresse nachgeladen. RFI setzt eine unsichere PHP-Einstellung voraus, LFI nicht.
Reicht es, ../ aus dem Parameter zu entfernen?
Nein. Solche Filter lassen sich oft durch Kodierung oder Varianten umgehen und übersehen absolute Pfade. Sicher ist nur eine feste Zuordnung von erlaubten Werten zu Dateien oder eine Prüfung des aufgelösten Pfads gegen ein Basisverzeichnis.
Schützt open_basedir vollständig vor File Inclusion?
Nein, es begrenzt nur die Verzeichnisse, auf die PHP zugreifen darf. Innerhalb dieser Grenzen bleibt eine Lücke ausnutzbar. open_basedir ist eine zusätzliche Schutzschicht und ersetzt keinen sicheren Code.
Sind CMS wie WordPress von LFI betroffen?
Der Kern gängiger CMS gilt in diesem Punkt als gut geprüft. Lücken tauchen eher in Erweiterungen, Themes und Eigenentwicklungen auf. Updates der Erweiterungen und ein Blick in deren Sicherheitsmeldungen sind daher wichtig.