CrossFit
Summary
CrossFit exposed FTPS, SSH, and an Apache-hosted gym website. Virtual-host discovery identified a separate FTP account-management application whose credentialed CORS policy trusted the gym origin. The gym site's security-report workflow stored each submitter's User-Agent and rendered it without encoding in a page reviewed by an administrator. A blind stored XSS payload could therefore execute inside the administrator's authenticated browser and cross the origin boundary into the FTP application.
The browser-side chain retrieved a CSRF token, created an FTP account, and granted that account write access to the gym site's web root. Uploading a PHP web shell produced code execution as www-data. A world-readable Ansible playbook then exposed Hank's password hash; cracking it offline yielded an SSH foothold and the user objective. From Hank's membership in the admins group, a cron-driven PHP task and its database configuration became readable. A crafted email value reached a vulnerable command-building library, allowing commands to run as Isaac and an SSH key to be installed for stable access.
The final escalation abused a root-owned database export utility. Its output filename was generated from a time-seeded pseudo-random value and could be predicted by an unprivileged local user. Pre-planting a symlink at the predicted path redirected the privileged export into /root/.ssh/authorized_keys. When the cron task fired, the attacker's public key was written into root's SSH configuration and the final objective became accessible.
Attack path — how the box was taken
Map the virtual hosts and their trust relationship, turn the security-report User-Agent into blind XSS, use the administrator's session to create an FTP account, upload a web shell, recover Hank's SSH credential, move laterally through a cron command-injection path, then redirect a predictable root export into root's authorized keys.
gym-club.crossfit.htb and ftp.crossfit.htb. The FTP application answered requests carrying the gym origin with both Access-Control-Allow-Origin for that origin and Access-Control-Allow-Credentials: true. Any script executing on the gym site could therefore issue authenticated cross-origin requests to the internal account-management application.[REDACTED: FTP password]. That account could upload through FTPS directly into the gym site's document root. A minimal PHP web shell placed there executed commands as www-data.hank, represented here as [REDACTED: password hash]. An offline dictionary attack recovered [REDACTED: hank SSH password].admins group, which later allowed inspection of Isaac's update task and its configuration. The user objective was readable from Hank's home directory.id returned uid=1004(hank) gid=1006(hank) groups=1006(hank),1005(admins); user.txt returned [REDACTED: user flag].Exact commands 3
export TARGET="<retired-instance-ip>"export HANK_PASSWORD="[REDACTED: hank SSH password]"sshpass -p "$HANK_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 hank@$TARGET 'id; cat /home/hank/user.txt'/home/isaac/send_updates/send_updates.php and the nearby database configuration. The database password is represented as [REDACTED: database password]. The script ran every minute as Isaac, selected email values from the CrossFit database, and passed them to php-shellcommand 1.6.0. That version was affected by CVE-2019-10774 and did not safely escape shell arguments. A metacharacter-bearing email value was inserted into the database, then the task was activated by placing a file in the FTP messages/ share. The resulting command created Isaac's .ssh directory and appended an attacker public key to authorized_keys.dbmsg utility periodically exported database messages into /var/local. Its filename came from srand(time()), rand(), and MD5, making the next path predictable to a local user with the same clock. From Isaac's shell, a database message containing the attacker's SSH public key was queued for export. Candidate names were calculated for the upcoming cron window, and a symlink was placed at the predicted path pointing to /root/.ssh/authorized_keys. When the privileged export opened that filename, it followed the symlink and wrote the key into root's SSH configuration.uid=0(root) gid=0(root) groups=0(root), and read [REDACTED: root flag] from root.txt.Exact commands 2
export TARGET="<retired-instance-ip>"for i in $(seq 1 45); do out=$(ssh -i /tmp/isaac_key -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=4 root@$TARGET 'id; cat /root/root.txt' 2>/dev/null); if echo "$out" | grep -q 'uid=0(root)'; then echo "$out"; exit 0; fi; sleep 1; done; echo root-key-not-readyAttack patterns used
The transferable techniques behind the compromise.
Blind stored XSS in an administrator workflowExploitationT1059.007
What it is
Attacker-controlled metadata was stored with a report and later interpreted as active markup when a privileged user reviewed it. The callback occurred outside the original request, which made the flaw blind from the submitter's perspective.
Why it works
Every stored value needs context-appropriate output encoding, including request headers and audit metadata. Administrative interfaces should also use a restrictive Content Security Policy and avoid rendering untrusted HTML.
Credentialed CORS as a trust bridgeExploitation
What it is
The internal FTP application trusted the externally reachable gym origin and permitted credentialed cross-origin requests. Once XSS supplied code execution in that origin, the browser carried the administrator's authenticated session across the application boundary.
Why it works
CORS is an access-control decision, not a convenience header. Credentialed origins must be narrowly justified, sensitive mutations need independent authorization, and CSRF protections must not assume a trusted origin is free of script injection.
Credential recovery from deployment filesCredential AccessT1552.001
What it is
A readable provisioning playbook contained a reusable operating-system password hash. Offline cracking converted the web-shell context into a stable SSH account.
Why it works
Configuration-management files often outlive their deployment purpose. Store secrets in a dedicated vault, keep playbooks least-readable, rotate reusable credentials, and remove hashes from production hosts.
Scheduled command injection through stored dataLateral MovementT1059.004
What it is
A database field that looked like application data crossed into a shell command during a scheduled task. The vulnerable command-building library allowed metacharacters in that field to become shell syntax under another user's identity.
Why it works
Never build shell commands from database values. Use direct process APIs with argument arrays, validate stored inputs again at the execution boundary, and run scheduled jobs with the least privilege they require.
Predictable privileged filename and symlink racePrivilege EscalationT1574.005
What it is
A root process created a predictable file in a directory an unprivileged user could influence. Pre-creating the path as a symlink redirected the privileged write into root's SSH configuration.
Why it works
Privileged programs should create files atomically in root-owned directories, use unpredictable kernel-backed names, refuse symbolic links, and verify file ownership and type immediately before writing.
Findings
Exposed services
| 21/tcp | ftps · FTP account storage mapped to web content and message queues |
| 22/tcp | ssh OpenSSH 7.9p1 Debian 10+deb10u2 |
| 80/tcp | http Apache 2.4.38 · gym and FTP-management virtual hosts |