Zum Hauptinhalt springen
RefoundRefound
Alle Artikel
securityphplegacy systems

5 Sicherheitsrisiken in verlassenen PHP-Anwendungen (und wie man sie behebt)

Refound

Die meisten PHP-Anwendungen, die vor 2018 geschrieben wurden, waren nie darauf ausgelegt, sicher zu sein — sie waren darauf ausgelegt, ausgeliefert zu werden. Die Frameworks waren lockerer, die Bedrohungslandschaft war anders, und der Entwickler, der sie gebaut hat, hat vor drei Jobs das Unternehmen verlassen.

Wenn Ihr Unternehmen auf einer PHP-Anwendung läuft, die nicht aktiv gewartet wurde, tragen Sie wahrscheinlich mindestens drei der folgenden fünf Risiken. Hier erfahren Sie, was sie sind, wie Sie sie finden und wie eine Behebung tatsächlich aussieht.


1. SQL-Injection durch unparametrisierte Abfragen

SQL-Injection ist seit über einem Jahrzehnt die häufigste Web-Schwachstelle. Sie ist in Legacy-PHP-Codebasen immer noch endemisch, da ältere PHP-Tutorials mysql_query() lehrten — eine Funktion, die in PHP 5.5 veraltet (deprecated) war und in PHP 7 vollständig entfernt wurde.

Wie es aussieht:

// Gefährlich — Benutzereingabe wird direkt in SQL verkettet
$username = $_POST['username'];
$result = mysql_query("SELECT * FROM users WHERE username = '$username'");

Ein einfaches Anführungszeichen im Benutzernamen bricht aus dem String-Kontext aus. Von dort aus kann ein Angreifer jede Zeile aus jeder Tabelle extrahieren, die Authentifizierung umgehen oder — auf schlecht konfigurierten Servern — Betriebssystembefehle ausführen.

Wie man es behebt:

Ersetzen Sie rohe Abfragen durch PDO Prepared Statements:

// Sicher — Eingabe ist parametrisiert und wird niemals als SQL interpretiert
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([':username' => $_POST['username']]);
$user = $stmt->fetch();

Wenn die Codebase groß ist, ist die Sanierung ein systematisches Audit und kein Ein-Tages-Sprint. Jede Datenbankinteraktion, die benutzergesteuerte Eingaben berührt, muss überprüft werden. Ein statisches Analysetool wie PHPStan mit dem richtigen Regelwerk wird die meisten davon schnell aufdecken.

Schweregrad: Kritisch. Automatisierte Scanner suchen ständig danach.


2. Composer-lose Codebasen mit EOL-Abhängigkeiten

Bevor Composer zum Standard wurde (ungefähr 2013–2015), bündelten PHP-Anwendungen Drittanbieter-Bibliotheken manuell — heruntergeladene Zip-Dateien, die in einen lib/- oder vendor/-Ordner geworfen, oft umbenannt und modifiziert wurden, ohne Versionsverfolgung und ohne Upgrade-Pfad.

Das Ergebnis: Ihre Anwendung verwendet mit an Sicherheit grenzender Wahrscheinlichkeit Bibliotheken mit bekannten CVEs, die niemals gepatcht werden, da es keinen Mechanismus gibt, um sie zu aktualisieren.

Wie man es auditiert:

# Wenn es keine composer.json gibt, ist das das erste Problem
ls composer.json || echo "Kein Composer — manuelles Abhängigkeitsaudit erforderlich"

# Für Apps, die Composer haben
composer audit

# PHP-Version überprüfen — EOL-Versionen erhalten keine Sicherheitspatches
php -v

PHP 8.0 hat das End-of-Life (EOL) im November 2023 erreicht. PHP 7.x ist uralt. Wenn Ihre Anwendung auf einer dieser Versionen läuft, arbeiten Sie ohne Sicherheitsnetz.

Was für die Behebung erforderlich ist:

  1. Inventarisieren Sie alle Drittanbieter-Codes in lib/, vendor/, includes/ — alles, was nicht in-house geschrieben wurde.
  2. Identifizieren Sie die Versionen anhand von CVE-Datenbanken (NVD, Snyk, etc.).
  3. Migrieren Sie zu Composer-verwalteten Äquivalenten oder modernen Alternativen.
  4. Etablieren Sie einen Prozess für laufende Abhängigkeitsaudits.

Das ist selten glamouröse Arbeit. Es gehört aber auch zu den sicherheitsrelevanten Aufgaben mit dem höchsten ROI, die Sie durchführen können.

Schweregrad: Hoch. Bekannte CVEs in ungepatchten Bibliotheken sind trivial ausnutzbar.


3. Unsichere Session-Handhabung

Die Standard-Session-Konfiguration von PHP wurde für Bequemlichkeit entwickelt, nicht für Sicherheit. In einer Produktionsanwendung, die nicht explizit gehärtet wurde, werden Sessions wahrscheinlich auf dem Dateisystem gespeichert, über HTTP übertragen und sind anfällig für Fixation-Angriffe.

Häufige Fehlkonfigurationen:

// Fehlt: Session-Regeneration nach Authentifizierung
session_start();
if ($valid_login) {
    // Kein session_regenerate_id() — anfällig für Session Fixation
    $_SESSION['user_id'] = $user_id;
}
; php.ini Standardwerte, die unsicher sind
session.cookie_httponly = 0  ; JavaScript kann Session-Cookies lesen
session.cookie_secure   = 0  ; Session-Cookie wird über HTTP übertragen
session.use_strict_mode = 0  ; Server akzeptiert extern gesetzte Session-IDs

Die sichere Konfiguration:

// Immer vor session_start() aufrufen
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);    // Erfordert HTTPS
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'Lax');

session_start();

// Session-ID bei Rechteausweitung (Privilege Escalation) regenerieren
if ($just_logged_in) {
    session_regenerate_id(true);
}

Wenn Sessions in einem gemeinsamen /tmp-Verzeichnis auf einer Shared-Hosting-Umgebung gespeichert werden — ein häufiges Legacy-Muster —, kann jeder Prozess auf diesem Server jede aktive Session lesen.

Schweregrad: Hoch. Session Hijacking ermöglicht eine vollständige Kontoübernahme, ohne das Passwort des Benutzers zu kennen.


4. Local File Inclusion (LFI) und Remote File Inclusion (RFI)

Ältere PHP-Anwendungen verwenden häufig include() oder require() mit Pfadsegmenten, die direkt aus Anfrageparametern (Request Parameters) übernommen werden. Dies war ein gängiges Muster für "Templating", bevor es MVC-Frameworks gab.

// Extrem gefährlich — Pfad wird durch Benutzereingabe kontrolliert
$page = $_GET['page'];
include("templates/$page.php");

Ein Angreifer übergibt page=../../../../etc/passwd%00 (Null-Byte-Injection in älteren PHP-Versionen oder direktes Path Traversal in fehlerhaft konfigurierten Setups) und liest beliebige Dateien vom Server. Mit allow_url_include = On kann er eine PHP-Datei einbinden, die auf einem entfernten Server gehostet wird, und beliebigen Code ausführen.

Wie das Audit aussieht:

# Jeden include/require finden, der Benutzereingaben berühren könnte
grep -rn "include\s*(\$_" .
grep -rn "require\s*(\$_" .
grep -rn "include\s*(.*\$_GET" .
grep -rn "include\s*(.*\$_POST" .

Die Lösung:

Konstruieren Sie Dateipfade niemals aus Benutzereingaben. Verwenden Sie eine Whitelist:

// Sicher — Benutzereingabe wählt nur aus einer als sicher bekannten Liste (Whitelist)
$allowed_pages = ['home', 'about', 'services', 'contact'];
$page = $_GET['page'] ?? 'home';

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

include("templates/{$page}.php");

Stellen Sie außerdem sicher, dass allow_url_include und allow_url_fopen in der php.ini deaktiviert sind:

allow_url_include = Off
allow_url_fopen   = Off

Schweregrad: Kritisch. LFI ist ein direkter Weg zur Remote-Code-Ausführung.


5. Ausführliche Fehlerausgabe in der Produktion

Dies wird leicht übersehen, weil es sich nicht wie ein Sicherheitsproblem anfühlt — es fühlt sich wie eine Erleichterung beim Debuggen an. Aber ein Stack-Trace, der einem nicht authentifizierten Benutzer angezeigt wird, enthält den vollständigen Dateipfad, die PHP-Version, das Datenbankschema und manchmal Anmeldeinformationen, die als Funktionsargumente übergeben wurden.

Was ein Angreifer aus einem einzigen PHP-Fehler lernt:

Fatal error: Uncaught PDOException: SQLSTATE[42000]:
Syntax error or access violation in
/var/www/html/sites/client-name/app/Models/User.php:47
Stack trace:
#0 /var/www/html/sites/client-name/app/Models/User.php(47):
PDO->prepare('SELECT * FROM u...')

Aus diesem einzigen Fehler: die Server-Pfadstruktur, das Layout des Frameworks, der Datenbanktreiber, der Tabellenname und die Tatsache, dass die Abfrage unparametrisiert ist.

Beheben Sie es in zwei Zeilen:

// In der Produktion: Anzeige deaktivieren, Protokollierung (Logging) aktivieren
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php/app-errors.log');
error_reporting(E_ALL);

Oder in der php.ini:

display_errors  = Off
log_errors      = On
error_log       = /var/log/php/errors.log

Ein ordnungsgemäßer Fehler-Handler auf Anwendungsebene sollte Exceptions abfangen, bevor sie zur PHP-Engine aufsteigen, und eine kontrollierte Fehlerantwort zurückgeben:

set_exception_handler(function (Throwable $e) {
    // Den vollständigen Trace intern protokollieren
    error_log($e->getMessage() . "\n" . $e->getTraceAsString());

    // Nichts Brauchbares an den Client zurückgeben
    http_response_code(500);
    echo json_encode(['error' => 'Ein unerwarteter Fehler ist aufgetreten.']);
    exit;
});

Schweregrad: Mittel (isoliert betrachtet), aber es verstärkt jede andere Schwachstelle — es gibt Angreifern die Schatzkarte in die Hand.


Wie dies in der Praxis aussieht

Diese fünf Risiken existieren nicht isoliert. Eine typische verlassene PHP-Anwendung weist SQL-Injection in der Authentifizierungsschicht, eine ungepatchte Drittanbieter-Bibliothek mit bekannter CVE und eine ausführliche Fehlerausgabe, die das Datenbankschema preisgibt, auf. Zusammen stellen sie einen Weg dar, von einer unauthentifizierten HTTP-Anfrage zur vollständigen Datenbankextraktion in unter zehn Minuten zu gelangen.

Die gute Nachricht: Keines dieser Risiken erfordert einen kompletten Rewrite. Ein strukturiertes Sicherheitsaudit, gefolgt von gezielter Behebung, kann eine Legacy-PHP-Anwendung in wenigen Wochen härten — nicht Monaten. Die Arbeit ist methodisch, nicht heroisch.

Wenn Ihre Anwendung in den letzten 12 Monaten keiner Sicherheitsüberprüfung unterzogen wurde, ist sie überfällig. Ein Projekt zur Sicherheitshärtung schließt diese Lücken systematisch — beginnend mit den kritischsten Risiken und arbeitend durch die vollständige OWASP Top 10-Checkliste. Für einen umfassenderen Blick auf Sicherheitsrisiken bei Legacy-Systemen lesen Sie unseren Leitfaden zu den OWASP Top 10 für Legacy-Anwendungen.