Description
I found a web app that can help process images: PNG images only!
Setup
Connect to the instance to access the PNG processing app link.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Understand the vulnerabilityObservationI noticed the app claimed to accept only PNG images, which suggested the server was performing some form of file validation that could be bypassed if both the magic-byte check and the filename check had exploitable gaps.See the file upload exploitation and web challenge bug patterns posts for the broader pattern. The validator does two checks:- PNG magic bytes (
89 50 4E 47 0D 0A 1A 0A) at the start of the file. - The substring
pngappears anywhere in the filename.
Learn more
File upload vulnerabilities occur when a server accepts user-supplied files without properly validating their content or handling them safely. They are one of the most impactful web vulnerabilities because a successful upload can lead directly to remote code execution (RCE): the attacker runs arbitrary commands on the server.
This challenge combines two flawed checks:
- Magic byte check: reading the first bytes of the file to confirm it "looks like" a PNG. An attacker can prepend valid PNG magic bytes (
89 50 4E 47 0D 0A 1A 0A) to any payload. - Filename substring check: the validator looks for the literal characters
pnganywhere in the filename, not the trailing extension.shell.png.phpcontainspngso it passes; the actual extension Apache uses for handler selection is.php, so the file gets executed by mod_php.
Proper validation requires an allowlist of trailing extensions, server-side MIME detection independent of the filename, and storing uploads outside the web root so they cannot be directly executed.
- PNG magic bytes (
Step 2
Download the PHP web shellObservationI noticed the server runs PHP (confirmed by the .php extension in the URL pattern), which suggested that uploading a PHP web shell would grant command execution once the file could bypass the upload filter.Download the phpbash.php script (or any simple PHP shell). This will be used to gain command execution on the server.Learn more
A web shell is a script uploaded to a web server that provides a browser-based interface for executing operating system commands. PHP web shells are common because PHP is the most widely deployed server-side scripting language. Even a minimal one-liner like
<?php system($_GET['cmd']); ?>gives full command execution.phpbash is a more polished web shell that renders a terminal-like interface in the browser, making it easier to navigate the file system interactively. It is widely used in CTF challenges because it is easy to use and publicly available.
In real penetration tests, web shells are uploaded during the exploitation phase and are considered a critical finding - they provide persistent access and full control over the web server process. Detection methods include file integrity monitoring, web application firewalls (WAFs), and anomaly detection on uploaded file content.
Step 3
Convert the shell to hexObservationI noticed that the PNG magic bytes include non-printable binary values (such as 0x89), which suggested that a text editor would corrupt them on save and that working in hex via CyberChef was necessary to keep every byte intact.Open the PHP shell in CyberChef's To Hex recipe so the bytes can be edited literally. A text editor would interpret the PNG magic (89 50 4E 47 0D 0A 1A 0A) as control characters and corrupt them on save; hex keeps every byte intact.Learn more
Converting to hex is an intermediate step needed to manually prepend the PNG magic bytes to the PHP file content in the next step. Working in hex lets you precisely control the byte sequence at the start of the file without worrying about encoding issues from text editors or copy-paste tools that might alter binary data.
CyberChef(the GCHQ Cyber Swiss Army Knife) is a browser-based tool for encoding, decoding, and transforming data. Its recipe-based pipeline lets you chain operations visually: "To Hex" the file, prepend the magic bytes as a hex string, then "From Hex" to download the final binary in a single workflow.
This hex-manipulation workflow is broadly applicable: the same technique is used to bypass upload filters in bug bounty hunting, construct custom file format exploits, and craft test cases for fuzzing parsers.
Step 4
Prepend PNG magic bytesObservationI noticed the validator reads the first bytes of the uploaded file to confirm it is a PNG, which suggested prepending the 8-byte PNG signature (89 50 4E 47 0D 0A 1A 0A) to the PHP shell would satisfy that check while leaving the PHP code intact for execution.Add the PNG file signature (found here) to the beginning of the hex data. This will trick the file type validation into thinking it's a valid PNG image.bash89 50 4E 47 0D 0A 1A 0AWhat didn't work first
Tried: Prepend only the ASCII text 'PNG' instead of the full 8-byte binary magic sequence.
The validator reads the raw bytes at offset 0, not a human-readable string. Writing the three ASCII characters P, N, G produces the bytes 50 4E 47 starting at offset 0, which does not match the required 89 50 4E 47 0D 0A 1A 0A sequence. The magic-byte check fails immediately. You must prepend the full 8-byte binary signature including the non-printable leading byte 89.
Tried: Append the PNG magic bytes at the end of the PHP file instead of the beginning.
Magic-byte validators read from offset 0 only. If the signature appears anywhere other than the very start of the file, the check sees the PHP opening tag first and rejects the upload as not-PNG. The full signature must come before all other content so the validator finds it at position 0.
Learn more
Magic bytes (also called file signatures) are the first few bytes of a file that identify its format. The PNG signature is the 8-byte sequence
89 50 4E 47 0D 0A 1A 0A. The bytes 50 4E 47 spell "PNG" in ASCII; the others are control characters chosen to detect common transmission corruptions (e.g., CRLF conversion, high-bit stripping).File type detection libraries (libmagic, which powers the
filecommand) read these bytes to identify formats. A validator that only checks magic bytes can be trivially fooled: the bytes are at a fixed offset, so any attacker can prepend them to any payload.- JPEG:
FF D8 FF - PDF:
25 50 44 46(%PDF) - ZIP/APK/JAR/DOCX:
50 4B 03 04(PK) - ELF (Linux executable):
7F 45 4C 46
- JPEG:
Step 5
Convert back to binaryObservationI noticed the CyberChef workflow had produced a hex string representing the polyglot payload, which suggested using the 'From Hex' recipe to convert it back to the actual binary file that the server would receive and store.Use CyberChef's "From Hex" recipe to convert the modified hex back to binary and download the file.Learn more
Converting back from hex to binary produces the actual file that will be uploaded. The resulting bytes start with the PNG signature (making magic-byte validators happy) followed immediately by valid PHP code (making the PHP interpreter happy when the server executes the file).
This two-format hybrid is a simple polyglot: a file that simultaneously satisfies two format parsers. The PNG parser reads the signature, finds corrupt data, and fails gracefully - it just won't render as a valid image. The PHP parser ignores the leading PNG bytes (treating them as binary output before the first
<?phptag) and executes the script.CyberChef's "Download output" button saves the binary to your file system with the correct raw bytes, avoiding any character encoding issues that could corrupt the payload.
Step 6
Upload with double extensionObservationI noticed the filename validator only checked whether the string 'png' appeared anywhere in the name rather than verifying the trailing extension, which suggested that naming the file shell.png.php would pass the substring check while Apache would still use the trailing .php to select the PHP handler.Upload as shell.png.php. The validator's substring check finds 'png' in the middle and lets the file through; Apache uses the trailing .php extension to pick the PHP handler and executes the file.Learn more
Double extension attacks exploit the gap between filename validation and the web server's extension-to-handler mapping. The validator here only checks for the substring
png, while Apache's handler selection picks the trailing.phpextension. The two checks disagree on what type of file this is, so the upload passes and the file still runs as PHP.Related bypass techniques include:
shell.php%00.png: null byte injection terminates the filename string in some older PHP versions, causing the.pngto be ignored.shell.pHp: case-variation bypass on case-insensitive file systems.shell.php7orshell.phtml: alternative PHP extensions that may be allowed but still execute.
Step 7
Access the web shellObservationI noticed the uploads directory was directly accessible under the web root, which suggested that navigating to the uploaded file's URL would cause Apache to pass it to the PHP interpreter and render the interactive shell.Visit the uploaded file's URL in your browser. Apache hands it to the PHP interpreter and you get an interactive shell in the page.bashhttp://atlas.picoctf.net:<PORT_FROM_INSTANCE>/uploads/filename.png.phpWhat didn't work first
Tried: Navigate to the URL using the original upload filename shell.php instead of shell.png.php.
The file was saved on disk under the double-extension name you provided at upload time. If you uploaded as shell.png.php, the server stores and serves it at that exact path. Requesting shell.php returns a 404 because no file by that name exists in the uploads directory.
Tried: Visit the /uploads/ directory listing URL expecting to browse files directly.
Apache directory listing is typically disabled on CTF instances, so hitting /uploads/ returns a 403 Forbidden rather than a file index. You must know the exact filename from the upload response or confirmation message and request that specific path to reach the web shell.
Learn more
The uploads directory being directly accessible under the web root is the critical mistake that makes this attack work. If uploaded files were stored outside the web root (e.g.,
/var/uploads/instead of/var/www/html/uploads/), the server could serve them as static files with a forced content type but the PHP interpreter would never execute them.When you navigate to the uploaded file's URL, Apache recognizes the
.phpextension and passes the file tomod_phpfor execution. The PHP engine runs your shell code and returns its output as the HTTP response body - giving you a fully interactive command execution environment in the browser.Mitigations in a secure upload implementation: store files outside the web root, randomize filenames, set
Content-Disposition: attachmentheaders to force downloads, and configure the upload directory withphp_flag engine offin.htaccessto prevent PHP execution even if a file slips through.Step 8
Find the flag fileObservationI noticed the web shell dropped me into /var/www/html/uploads, and CTF flag files are typically placed one directory up from the web root, which suggested running ls ../ to reveal the randomly named .txt flag file.The shell starts in /var/www/html/uploads. Listing the parent directory reveals a randomly named .txt file containing the flag; the exact name varies per instance, so don't memorize it.bashpwdbashls ../What didn't work first
Tried: Run ls without a path argument expecting to find the flag file in the uploads directory.
The shell starts in /var/www/html/uploads where your uploaded PHP file lives, not where the flag is stored. Running bare ls only shows other uploaded files. The flag .txt file is placed one level up in /var/www/html/, so you must run ls ../ to see it.
Tried: Run find / -name '*.txt' to locate the flag file by extension across the whole filesystem.
This command works but produces hundreds of lines of output from system .txt files (/usr/share/doc, /etc, man pages) mixed with permission-denied errors, making it slow and hard to read. The flag file is predictably one directory up from the web root, so ls ../ is faster and gives a clean one-line result.
Learn more
After gaining shell access, the first step is always reconnaissance: where are you, what is around you, and what can you access?
pwd(print working directory) andlsare the foundational navigation commands.Web application files are typically served from
/var/www/html/on Debian/Ubuntu-based systems. Configuration files, database credentials, and other sensitive data are often stored one level up from the web root or in sibling directories. Listing../quickly reveals the broader directory structure.In real penetration testing, post-exploitation enumeration would continue with commands like
id(who am I?),uname -a(what OS/kernel?),cat /etc/passwd(what users exist?), andfind / -perm -4000 2>/dev/null(what SUID binaries can I exploit for privilege escalation?).Step 9
Read the flag fileObservationI noticed ls ../ revealed a randomly named .txt file, which suggested it was the flag file placed there by the challenge and that cat ../<flag-filename>.txt would print its contents.Once you have the filename from the previous ls, cat it to print the flag.bashcat ../<flag-filename>.txtExpected output
picoCTF{c3rt!fi3d_Xp3rt_tr1ckst3r_ab0e...}Learn more
The flag file has a randomly generated name per instance. This simulates a real-world scenario where sensitive data has an unpredictable filename and you can't guess it without either directory traversal or shell access. It reinforces why achieving code execution is significant: you can enumerate the file system rather than guess paths.
catoutputs file contents to stdout. The../path navigates one directory up from the current working directory.In real incident response, finding unexpected files in
/var/www/html/, especially with suspicious names or recent modification timestamps, is a strong indicator of compromise. File integrity monitoring (FIM) tools like Tripwire or AIDE detect unauthorized additions to web directories.
Flag
Reveal flag
picoCTF{c3rt!fi3d_Xp3rt_tr1ckst3r_ab0e...}
Once the shell is uploaded, the flag text file is one directory up from the uploads folder.