Description
The profile-picture upload endpoint accepts any file, drops it in /uploads, and serves it back. Upload a PHP web shell, browse to it, and use sudo to read /root/flag.txt.
Setup
Download phpbash.php (single-file, decade-tested PHP web shell - weevely is more featureful but staging it is overkill here).
Upload it via the avatar form and note the returned path (e.g., uploads/phpbash.php).
Browse to /uploads/phpbash.php to verify execution: an interactive terminal UI means PHP ran. If you see raw <?php source instead, the server isn't passing .php files to the interpreter and you'd need a different filename or extension.
wget https://raw.githubusercontent.com/Arrexel/phpbash/master/phpbash.php# In the web shell, recon first:id; pwd; sudo -lsudo cat /root/flag.txtSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Gain a shellObservationI noticed the challenge description said the upload endpoint accepts any file and serves it back from /uploads, which meant uploading a .php file to a web-accessible directory would cause the PHP interpreter to execute it as a web shell.Browse tohttp://host/uploads/phpbash.php. The terminal UI confirms the server executed PHP.idreportsuid=33(www-data),pwdlands you in/var/www/html/uploads, andsudo -llists what root will let www-data run without a password.bash# In the phpbash terminal: id # uid, gid, groups - matters for sudo and file ACLs pwd # confirm you're in the upload dir under the web root sudo -l # what can www-data run as root, NOPASSWD or otherwise ls -la /var/www/htmlExpected output
picoCTF{wh47_c4n_u_d0_wPHP_5f89...}What didn't work first
Tried: Uploading a .php file but visiting the URL and seeing the raw PHP source instead of a terminal UI.
Some servers (or misconfigured Apache/Nginx vhosts) serve .php files as plain text rather than passing them to the PHP interpreter. Renaming the shell to .php5 or .phtml can bypass extension-based interpreter mapping, but the root issue is that the server's PHP handler is not configured for the upload directory. Confirming execution requires seeing the phpbash terminal UI - raw source means no interpreter involvement.
Tried: Uploading the file as image/jpeg in the Content-Type header hoping the server requires an image type.
The server in this challenge does not validate content type at all, so this is unnecessary. If a server does reject non-image MIME types, sending Content-Type: image/jpeg with a .php payload can bypass the check because the server trusts the client-supplied header rather than inspecting the file's actual bytes. The correct test is to attempt a plain upload first and only add MIME spoofing if the server responds with a rejection error.
Learn more
Unrestricted file upload is one of the most critical web vulnerabilities (OWASP Top 10 A04). When a server accepts arbitrary files without validating their content type or extension, an attacker can upload executable scripts. On a PHP server, uploading a
.phpfile to a web-accessible directory and visiting its URL causes Apache or Nginx to pass it to the PHP interpreter, giving the attacker a web shell.phpbash is a popular open-source web shell that provides a terminal-like interface in the browser. Once served, it executes system commands as the web server's user (typically
www-data). Proper mitigations include validating file content with MIME-type inspection (not just extension), storing uploads outside the web root, serving them through a dedicated file-serving endpoint that strips executable permissions, and using a CDN or object storage service (S3, GCS) rather than the same server.This vulnerability class has enabled some of the most impactful breaches in history. Bypasses exist for naive extension blacklists: using
.php5,.phtml,.PhP(case variation), or embedding a null byte in some older systems. Content-type whitelisting at the HTTP header level is also bypassable because the client sends that header and can lie about it.Step 2
Escalate with sudoObservationI noticed that runningsudo -lin the phpbash web shell returned(ALL) NOPASSWD: ALL, which indicated www-data had unrestricted passwordless sudo access and made reading /root/flag.txt a single command away.sudo -lprints(ALL) NOPASSWD: ALL, sosudo cat /root/flag.txtprints the flag with no further effort. If the bare command ever fails (TTY weirdness in a web shell), fall back tosudo -u root cat /root/flag.txtor wrap it viasudo bash -c 'cat /root/flag.txt'.bashsudo -lbash# Expected: (ALL) NOPASSWD: ALLbashsudo ls /rootbashsudo cat /root/flag.txtbash# Fallbacks if the simple form misbehaves:bashsudo -u root cat /root/flag.txtbashsudo bash -c 'cat /root/flag.txt'What didn't work first
Tried: Running sudo cat /root/flag.txt without first checking sudo -l, then getting a password prompt and stalling.
If sudo requires a password, the web shell has no TTY to accept input and the command hangs or exits with a non-interactive error. Running sudo -l first confirms the NOPASSWD policy before relying on it. Without that confirmation step, an attacker wastes time debugging TTY issues rather than recognizing that the policy may require a password.
Tried: Trying su root or switching to the root account directly from the web shell instead of using sudo.
su requires the root user's password, which is unknown, and also needs a real TTY to accept it - web shells provide neither. sudo with NOPASSWD bypasses both requirements because it authenticates based on policy in /etc/sudoers rather than the account password. su will always fail here; the correct path is sudo with the specific command you want to run as root.
Learn more
Passwordless sudo (
NOPASSWD) is a privilege escalation shortcut that grants a user the ability to run commands as root without entering a password. It is configured in/etc/sudoerswith lines likewww-data ALL=(ALL) NOPASSWD: ALL. While useful for automation, granting unrestrictedNOPASSWDto a web process is catastrophically insecure.In a real penetration test, post-exploitation privilege escalation typically involves checking
sudo -lto list allowed commands, looking for SUID binaries (find / -perm -4000), searching for writable cron jobs or services, and reviewing/etc/sudoersand/etc/sudoers.d/. The combination of web shell plus NOPASSWD sudo is one of the fastest paths from unauthenticated access to full root compromise.The principle of least privilege dictates that web servers should run as a dedicated low-privilege user with no sudo rights, no write access outside their document root, and no ability to read system files. Container-based deployments add another layer of isolation - even if an attacker gains a shell inside a container, they face additional barriers before reaching the host.
Interactive tools
- Reverse Shell GeneratorGenerate reverse shell payloads (bash, nc, python, perl, ruby, php, node, powershell) and matching listeners. Set host and port once, copy any variant.
Flag
Reveal flag
picoCTF{wh47_c4n_u_d0_wPHP_5f89...}
File uploads should validate type and prevent arbitrary execution paths.
Key takeaway
How to prevent this
How to prevent this
This whole chain collapses if any one of these controls is in place. None are exotic; they show up in every web framework's docs.
- Validate uploads by content, not extension. Inspect magic bytes server-side and reject anything the renderer would execute (
.php,.phtml,.phar,.jsp,.aspx). - Store uploads outside the web root, or on object storage (S3, GCS, Vercel Blob). Serve them through a handler that sets
Content-Disposition: attachmentand a fixed MIME type so the server never interprets them. - Run the web process as an unprivileged user with zero sudo rights.
NOPASSWD: ALLon a www-data account turns any RCE into instant root.