← all walkthroughs

CrossFit

Linux· Insane· Credential Access· Privilege Escalation
owned
2026-07-13
time to own
17m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

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.

1EnumerationVirtual-host discovery and CORS analysis (T1590)
Mapped the public gym site and its trusted FTP-management origin
The exposed surface consisted of FTPS on TCP 21, SSH on TCP 22, and Apache on TCP 80. Host-header enumeration uncovered 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.
2ExploitationBlind stored Cross-Site Scripting (T1059.007)
Injected a remote script through the stored User-Agent field
The gym site's security-report form recorded the submitter's User-Agent and later rendered it without HTML encoding in an administrator's review page. Submitting a report with a script tag in that header placed a blind stored XSS payload in the review queue. When the administrator opened the report, the browser fetched and executed the attacker-controlled JavaScript in the trusted gym origin.
3ExploitationCredentialed CORS, CSRF token theft, and web-shell upload (T1505.003)
Created an FTP account in the administrator's session and wrote into the web root
The injected script made a credentialed request to the FTP application's account-creation page, extracted the CSRF token from the response, and submitted a second request that created an attacker-controlled FTP user. The recovered FTP password is represented as [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.
4Credential AccessCredentials in files and offline password cracking (T1552.001)
Recovered Hank's password hash from a world-readable Ansible playbook
Filesystem review from the web-shell context found an Ansible user-provisioning playbook that was readable by unprivileged users. It contained a SHA-512 crypt hash for the local account hank, represented here as [REDACTED: password hash]. An offline dictionary attack recovered [REDACTED: hank SSH password].
5FootholdValid account over SSH (T1078.003)
Authenticated as hank and captured the user objective
The cracked credential was accepted by SSH. The resulting shell ran as UID 1004 and showed Hank in the 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
Bind the retired instance address locally.
export TARGET="<retired-instance-ip>"
Bind the recovered password without disclosing it.
export HANK_PASSWORD="[REDACTED: hank SSH password]"
Confirm the SSH identity and read the user objective.
sshpass -p "$HANK_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 hank@$TARGET 'id; cat /home/hank/user.txt'
6Lateral MovementCron-driven command injection through php-shellcommand (T1059.004)
Turned a database email value into command execution as isaac
Hank's group access exposed /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.
7Privilege EscalationPredictable filename and privileged symlink write (T1574.005)
Redirected the root-owned database export into root's authorized keys
The root-owned 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.
The polling loop authenticated as root with Isaac's key, returned uid=0(root) gid=0(root) groups=0(root), and read [REDACTED: root flag] from root.txt.
Exact commands 2
Bind the retired instance address if this is a fresh shell.
export TARGET="<retired-instance-ip>"
Poll for the privileged export and stop after root ownership is proved.
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-ready

Attack 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

Blind stored XSS and credentialed CORS exposed FTP administrationCritical
An unencoded User-Agent executed in the administrator's browser, while the FTP application trusted that origin with credentials. Together they allowed an attacker to create a web-root-capable FTP account.
Deployment credentials and an unsafe scheduled command enabled lateral movementHigh
A readable playbook exposed Hank's password hash, and a scheduled task passed database-controlled email values into a vulnerable shell-command library running as Isaac.
Predictable root export paths enabled an arbitrary privileged file writeCritical
The root-owned exporter used a predictable filename in an attacker-influenced directory and followed a pre-positioned symlink into root's authorized keys.

Exposed services

21/tcp
22/tcp
80/tcp

Operational notes

The foothold depended on a browser trust chain
Neither blind XSS nor permissive CORS completed the compromise alone. The important path was their composition: script execution in a trusted origin, authenticated cross-origin access, account creation, then web-root write access.
Several stages were asynchronous
The administrator review, Isaac's minute-based update task, and the root export cron all introduced timing boundaries. A missing callback or delayed key did not invalidate the technique until the relevant job window had elapsed.
Stable access reduced race ambiguity
Planting an SSH key for Isaac separated the lateral-movement result from the final filename race. That made clock alignment, candidate generation, symlink placement, and root authentication easier to reason about independently.