← all walkthroughs

Player

Linux· Hard· Web
owned
2026-07-10
time to own
47m36s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I used host-header fuzzing to surface three hidden virtual hosts on the Apache web server, then recovered the HS256 JWT signing secret from a PHP source-code backup file that Apache served without restriction. A forged JWT granted authenticated access to a PlayBuff video-upload application, whose unpatched FFmpeg build was exploited via CVE-2016-1897/1898 to read arbitrary local files by embedding malicious HLS playlists in crafted AVI uploads.

Those reads exposed a service configuration file containing the plaintext SSH password for the account 'telegen'. Connecting to a second SSH service on port 6686 with those credentials succeeded, but the session was confined to a lshell restricted shell.

OpenSSH 7.2 on that port is vulnerable to CVE-2016-3115: the xauth mechanism passed the client-supplied X11 display name to a shell invocation without sanitizing metacharacters, allowing command injection that bypassed the shell restriction entirely. A ported Python proof-of-concept exploited this primitive to read both the user and root flag files directly from disk — full system compromise without a separate privilege-escalation exploit.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD4="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationActive network scanning and virtual host enumeration (T1595, T1046)
Discovered all services and hidden virtual hosts via port scanning and host-header fuzzing
A full TCP port scan revealed three listening services: SSH on port 22 (OpenSSH 7.2p2), Apache 2.4.18/PHP on port 80, and a second SSH listener on the non-standard port 6686 running OpenSSH 7.2 — an older, unpatched build. Content-fuzzing on player.htb uncovered a /launcher directory. Host-header fuzzing against the Apache server surfaced three hidden virtual hosts — chat.player.htb, dev.player.htb, and staging.player.htb — each serving a distinct application. Messages on chat.player.htb disclosed that staging.player.htb exposed sensitive backup files and that the main domain leaked application source code, providing precise targets for subsequent steps.
Nmap confirmed 6686/tcp open OpenSSH 7.2; ffuf returned HTTP 200 for chat, dev, and staging subdomains; chat messages referenced /var/www/backup/ and source exposure.
Exact commands 4
Full TCP port scan — discovers ports 22, 80, and 6686.
nmap -sC -sV -p- --min-rate 5000 $TARGET
Register all discovered vhosts for local name resolution.
echo "$TARGET player.htb chat.player.htb dev.player.htb staging.player.htb" | sudo tee -a /etc/hosts
Directory fuzz on the root vhost — reveals /launcher.
ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://player.htb/FUZZ -mc all -fc 404 -t 50
Host-header fuzz — surfaces chat, dev, and staging virtual hosts.
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://$TARGET/ -H 'Host: FUZZ.player.htb' -mc 200,301,302,403 -fc 404 -t 50
2Source DisclosureWeb server backup file disclosure (CWE-538)
Fetched a PHP editor backup file to extract the hardcoded JWT signing key
The launcher application was served by a PHP file with an obfuscated filename. Apache's default configuration serves all file extensions, including editor-generated backup variants such as .php~ (tilde-suffixed files created by editors like Emacs and Vim). Appending a tilde to the filename in the URL returned the raw PHP source code. The source contained a hardcoded Firebase-JWT library call with the signing key [REDACTED: recovered credential] embedded directly in the code, providing everything needed to forge arbitrary valid session tokens.
Curl http://player.htb/launcher/[REDACTED: sensitive value].php~ returned PHP source containing the hardcoded Firebase-JWT signing key.
Exact commands 2
Access the launcher — the server issues an access JWT cookie, revealing the cookie name and token structure.
curl -sS http://player.htb/launcher/[REDACTED: sensitive value].php
Append tilde to fetch the editor backup file; Apache serves it unguarded, returning the PHP source with the embedded signing key.
curl -sS http://player.htb/launcher/[REDACTED: sensitive value].php~
FixBlock Apache from serving editor backup and temporary filesHigh
WeaknessApache served the tilde-suffixed backup file [REDACTED: sensitive value].php~ as plain text with no authentication requirement, exposing the complete PHP application source — including the embedded JWT signing key — to any unauthenticated HTTP client.
FixAdd a FilesMatch directive to the Apache virtual-host configuration or a site-wide .htaccess to deny requests for common backup extensions: <FilesMatch "\.(php~|bak|swp|orig|old|phps)$"> Require all denied </FilesMatch>. Also configure the deployment pipeline so that editor swap and backup files are never copied into the production document root.
3Authentication BypassJWT forgery with a known HS256 signing secret (CWE-347)
Forged a valid HS256 JWT to bypass the launcher access gate
The launcher verified the cookie JWT's signature against the hardcoded key but imposed no other restrictions on the claims. With the recovered secret, a new HS256 JWT was crafted with an arbitrary access_code claim value. The forged token passed the signature check and granted full authenticated access to the PlayBuff video-upload interface, unlocking the upload functionality used in the next step.
Forged token accepted without error; server returned the PlayBuff upload page with HTTP 200.
Exact commands 2
Forge the HS256 JWT using the recovered key. Adjust key treatment (raw bytes vs. Urlsafe-b64-decoded) to match what the PHP source shows.
python3 -c "import json,hmac,hashlib,base64; key=b'$PASSWORD'; h=lambda o: base64.urlsafe_b64encode(json.dumps(o,separators=(',',':')).encode()).rstrip(b'=').decode(); hdr=h({'typ':'JWT','alg':'HS256'}); pay=h({'project':'PlayBuff','access_code':'0'}); sig=base64.urlsafe_b64encode(hmac.new(key,f'{hdr}.{pay}'.encode(),hashlib.sha256).digest()).rstrip(b'=').decode(); print(f'{hdr}.{pay}.{sig}')"
Replace <FORGED_JWT> with the printed token; a 200 response with the PlayBuff UI confirms authenticated access.
curl -sS -b 'access=<FORGED_JWT>' http://player.htb/launcher/[REDACTED: sensitive value].php
FixReplace the hardcoded JWT signing secret with an environment-injected cryptographic keyCritical
WeaknessThe launcher PHP application embedded the string [REDACTED: recovered credential] directly in source code as the HS256 JWT signing key. Anyone who reads the source — through a backup file, a repository leak, or any file-read primitive — can immediately forge valid session tokens with arbitrary claims and bypass authentication entirely.
FixGenerate a cryptographically random 256-bit secret (openssl rand -hex 32), store it as an environment variable or in a secrets manager, and load it at runtime. Remove all credentials from the codebase and rotate the current key immediately. Consider switching to RS256 asymmetric signing so the private key never needs to leave a secured secrets store, and the public key used for verification carries no signing capability.
4ExploitationSSRF/LFI via FFmpeg HLS demuxer (CVE-2016-1897, CVE-2016-1898)
Exploited unpatched FFmpeg (CVE-2016-1897/1898) to exfiltrate arbitrary local files via crafted AVI upload
The PlayBuff video-upload endpoint processed my own AVI files directly with an unpatched FFmpeg build. CVE-2016-1897 and CVE-2016-1898 describe an HLS demuxer file-read vulnerability: a crafted AVI can embed a malicious HLS playlist that instructs FFmpeg to open a local file:/// URI and encode the file's raw bytes into the output video's pixel data. Using the gen_xbin_avi.py generator from PayloadsAllTheThings, I crafted AVIs targeting /etc/passwd, /var/www/backup/service_config, and dev.player.htb's Codiad user database. Uploading each AVI and then extracting the response video frames with ffmpeg, applying ImageMagick contrast enhancement, and running tesseract OCR produced readable text for each target file.
OCR of extracted AVI frames returned readable /etc/passwd entries, the Codiad SHA-1 hash for peter, and the IMAP credential block for telegen in service_config.
Exact commands 4
Generate a malicious AVI embedding an HLS playlist that reads /etc/passwd. Obtain gen_xbin_avi.py from PayloadsAllTheThings 'Upload Insecure Files / CVE FFmpeg HLS'.
python3 gen_xbin_avi.py /etc/passwd
Upload the crafted AVI; FFmpeg processes it and returns a video encoding the target file contents.
curl -sS -b 'access=<FORGED_JWT>' -F 'video=@malicious.avi' http://player.htb/launcher/[REDACTED: sensitive value].php -o response.avi
Extract individual frames from the output video.
ffmpeg -i response.avi -vf fps=5 frame%04d.png
Contrast-enhance the frame and OCR it to recover the exfiltrated file text.
convert -resize 300% -colorspace Gray -sharpen 0x1 frame0001.png enhanced.png && tesseract enhanced.png stdout
FixUpgrade FFmpeg and run media processing in an isolated unprivileged sandboxCritical
WeaknessThe video-upload endpoint processed externally supplied media with an unpatched FFmpeg build vulnerable to CVE-2016-1897/1898. A crafted AVI embedding an HLS playlist could instruct FFmpeg to open local file:/// URIs and encode file contents into the output video, giving any authenticated (or token-forging) user read access to any file readable by the web server process.
FixUpgrade FFmpeg to a version that restricts local-URI access in the HLS demuxer (patched in upstream 2016 builds). Run the media-processing worker as a dedicated unprivileged OS account isolated from the web root, credential stores, and home directories. Apply an AppArmor or seccomp profile to restrict the worker's syscall surface. Validate that each upload is genuine video/image content by checking magic bytes before handing the file to FFmpeg, and reject anything that does not pass.
5Credential HarvestingCredential extraction from cleartext configuration files (T1552.001)
Extracted the plaintext telegen SSH password from the leaked service configuration
Repeating the FFmpeg LFI technique against /var/www/backup/service_config — a credential file stored inside the Apache document root — yielded an IMAP service block containing the plaintext password [REDACTED: recovered credential] for the Linux account telegen. The same technique against dev.player.htb's Codiad IDE user store (data/users.php) returned the SHA-1 password hash [REDACTED: recovered credential] for the account peter. The telegen credential was immediately actionable against the SSH services.
Service_config OCR: user = telegen, password = [REDACTED: recovered credential]; data/users.php OCR: peter / SHA-1 [REDACTED: recovered credential].
Exact commands 3
Generate the AVI targeting the credential file.
python3 gen_xbin_avi.py /var/www/backup/service_config
Upload, extract frames, and OCR the service_config leak in one pipeline.
curl -sS -b 'access=<FORGED_JWT>' -F 'video=@malicious.avi' http://player.htb/launcher/[REDACTED: sensitive value].php -o svc_cfg.avi && ffmpeg -i svc_cfg.avi -vf fps=5 svc%04d.png && convert -resize 300% -colorspace Gray -sharpen 0x1 svc0001.png svc_enh.png && tesseract svc_enh.png stdout
Repeat for the Codiad user database to harvest the peter account hash.
python3 gen_xbin_avi.py /var/www/html/dev/data/users.php
FixRemove credential files from web-accessible and FFmpeg-reachable pathsHigh
WeaknessThe IMAP service configuration file containing the plaintext telegen SSH password was stored at /var/www/backup/service_config — a path under the Apache document root. Any file-read primitive reachable by the web server process, including the FFmpeg LFI exploited in steps 4 and 5, could retrieve it without any additional access control.
FixMove all credential and configuration files to paths outside the document root (e.g., /etc/playerapp/ or /opt/app/secrets/). Set file permissions so only the specific service account that requires the credential can read it (chmod 600, chown [REDACTED: recovered credential]:[REDACTED: recovered credential]). Audit the entire web root and any backup subdirectories for sensitive files and relocate or delete them.
6FootholdValid account credential reuse against a network service (T1078)
Authenticated to the restricted SSH service on port 6686 as telegen, landing in lshell
The recovered telegen password was tested against both SSH listeners. Port 6686 authenticated successfully. The session was immediately confined to lshell (/usr/bin/lshell), a Python-based restricted shell with a narrow command whitelist that blocked arbitrary file reads, shell escapes, and network utilities — initially preventing further progress. The confinement signalled that a shell-bypass technique would be needed rather than a traditional privilege-escalation exploit.
SSH handshake on port 6686 accepted telegen / [REDACTED: recovered credential]; session prompt identified the environment as lshell.
Exact commands 2
Confirm the OpenSSH 7.2 version and supported authentication methods on the high-port service.
nmap -sV -p 6686 --script ssh-auth-methods $TARGET
Authenticate with password [REDACTED: recovered credential]; observe that the session lands in the lshell restricted shell rather than bash.
ssh -p 6686 telegen@$TARGET
7Privilege EscalationOpenSSH xauth command injection / restricted-shell bypass (CVE-2016-3115, T1068)
Exploited CVE-2016-3115 xauth command injection to bypass lshell and capture both flags
OpenSSH 7.2 (without the p2 security patch applied to port 22) is vulnerable to CVE-2016-3115. When X11 forwarding is negotiated, the server passes the client-supplied X11 display name to the xauth binary via a shell invocation without sanitizing shell metacharacters. Anyone who supplies a display name such as 'x && cat /path/to/file #' causes those commands to run in the sshd process context — entirely bypassing any restricted-shell constraint set on the account. The public Exploit-DB proof-of-concept (EDB-39569) was ported from Python 2 to Python 3 with paramiko, then used to inject file-read payloads for /home/telegen/user.txt and /root/root.txt. Both flag files were returned in the xauth output channel, confirming that the injection reached root-owned paths and that no separate local privilege-escalation exploit was required for full system compromise.
Python3 xauth_read.py output: /home/telegen/user.txt → <user.txt>; /root/root.txt → <root.txt>.
Exact commands 4
Locate the CVE-2016-3115 xauth injection PoC on the local Exploit-DB mirror.
searchsploit -p 39569
Install the Python SSH library needed after porting the PoC from Python 2 to Python 3.
pip install paramiko
Exploit CVE-2016-3115 to inject a file-read command via the xauth display-name path; output contains user.txt.
python3 xauth_read.py $TARGET 6686 telegen '$PASSWORD4' /home/telegen/user.txt
Same primitive targeting root.txt — confirms root-level file read access via the sshd injection path.
python3 xauth_read.py $TARGET 6686 telegen '$PASSWORD4' /root/root.txt
FixPatch OpenSSH on port 6686 to fix CVE-2016-3115 and replace lshell with a genuine isolation boundaryCritical
WeaknessThe SSH service on port 6686 ran OpenSSH 7.2 without the p2 security patch. CVE-2016-3115 allows the xauth code path to execute the client-supplied X11 display name in a shell without sanitizing metacharacters, enabling command injection that completely bypassed the lshell restricted shell and gave an unauthorised user arbitrary file read access including root-owned files.
FixApply the distribution's OpenSSH security update to reach version 7.2p2 or later (verify with ssh -V). Replace lshell with a proper containment mechanism — a chroot jail, rbash with a locked-down PATH and no write access to PATH entries, or a container boundary — as lshell is not a reliable security control against an underlying sshd vulnerability. If the high-port SSH service has no specific operational purpose, disable it and block port 6686 at the host firewall.

Attack patterns used

The transferable techniques behind this compromise.

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting an unauthorised user read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

Exposed services

22/tcp
80/tcp
6686/tcp