Skip to main content

n0s4n1ty 1 picoCTF 2025 Solution

An unrestricted upload accepts a PHP file for command execution, and a passwordless sudo entry gives root.

Published: April 2, 2025Updated: August 25, 2026

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.

Web

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.

bash
wget https://raw.githubusercontent.com/Arrexel/phpbash/master/phpbash.php
bash
# In the web shell, recon first:
bash
id; pwd; sudo -l
bash
sudo cat /root/flag.txt

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
Unrestricted file upload + NOPASSWD sudo is one of the most reliable RCE-to-root chains; the File Upload Exploitation guide walks through web-shell variants, extension bypasses, and the post-exploitation Linux recon worth running every time. The Burp Suite for picoCTF guide covers the Repeater workflow for editing the upload's filename and Content-Type on the wire when the browser will not let you.
  1. Step 1Gain a shell
    Observation
    The upload endpoint takes any file and serves it back from /uploads. Put a .php file there and the interpreter runs it as a web shell.
    Browse to http://host/uploads/phpbash.php. The terminal UI confirms the server executed PHP. id reports uid=33(www-data), pwd lands you in /var/www/html/uploads, and sudo -l lists 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/html

    Expected 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 vhosts serve .php files as plain text instead of handing them to the interpreter. Renaming to .php5 or .phtml can get around extension-based mapping, but the real issue is that no PHP handler is configured for the upload directory. Seeing the terminal UI means it executed; seeing source means it did not.

    Tried: Uploading the file as image/jpeg in the Content-Type header hoping the server requires an image type.

    This server does not check content type at all, so the spoofing is unnecessary. Where a server does reject non-image types, a claimed image/jpeg header on a PHP payload often passes, because the check trusts the client rather than the bytes. Try the plain upload first and add spoofing only if it is rejected.

    Learn more

    Unrestricted file upload is one of the most critical web vulnerabilities (CWE-434, Unrestricted Upload of File with Dangerous Type). 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 .php file 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.

  2. Step 2Escalate with sudo
    Observation
    sudo -l in the web shell reports NOPASSWD for everything, so www-data has unrestricted passwordless sudo. Reading the flag is one command.
    sudo -l prints (ALL) NOPASSWD: ALL, so sudo cat /root/flag.txt prints the flag with no further effort. If the bare command ever fails (TTY weirdness in a web shell), fall back to sudo -u root cat /root/flag.txt or wrap it via sudo bash -c 'cat /root/flag.txt'.
    bash
    sudo -l
    bash
    # Expected: (ALL) NOPASSWD: ALL
    bash
    sudo ls /root
    bash
    sudo cat /root/flag.txt
    bash
    # Fallbacks if the simple form misbehaves:
    bash
    sudo -u root cat /root/flag.txt
    bash
    sudo 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 wants a password, a web shell has no TTY to type it into, so the command hangs or fails non-interactively. Running sudo -l first confirms the NOPASSWD policy, which saves debugging a TTY problem that is really a policy problem.

    Tried: Trying su root or switching to the root account directly from the web shell instead of using sudo.

    su needs the root password, which you do not have, and a real TTY to type it into, which a web shell does not provide. sudo under NOPASSWD needs neither, authenticating from the sudoers policy rather than an account password. su cannot work here.

    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/sudoers with lines like www-data ALL=(ALL) NOPASSWD: ALL. While useful for automation, granting unrestricted NOPASSWD to a web process is catastrophically insecure.

    In a real penetration test, post-exploitation privilege escalation typically involves checking sudo -l to list allowed commands, looking for SUID binaries (find / -perm -4000), searching for writable cron jobs or services, and reviewing /etc/sudoers and /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

Unrestricted upload becomes remote code execution the moment a file lands somewhere web-accessible that the server will interpret. That takes two separate failures: a handler that never validates content type or extension, and a directory configured to execute scripts rather than serve them. Add a sudo policy granting the web process passwordless root and you have one of the shortest paths from anonymous visitor to full compromise, a chain that breach reports from CMS and file-sharing platforms repeat constantly.

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: attachment and a fixed MIME type so the server never interprets them.
  • Run the web process as an unprivileged user with zero sudo rights. NOPASSWD: ALL on a www-data account turns any RCE into instant root.

Related reading

Tools used in this challenge

Where to go next