In this post, we’re going to solve the PortSwigger lab: “Web shell upload via extension blacklist bypass”.

To solve the lab, we need to upload a PHP file that reads and displays the contents of the /home/carlos/secret file. Since to demonstrate that we’ve completed the lab, we must enter the contents of this file.
Additionally, the server is configured to reject certain extensions.
In this case, the lab itself provides us with an account to log in, so let’s do it:


Once we’ve logged in, we find ourselves on the account profile:

As we can see, we have an option to upload a file, and specifically it appears to be to update the profile avatar. Let’s try to take advantage of this option to upload the following PHP file:

First of all, let’s prepare Burp Suite to intercept the request:


Once we have Burp Suite ready along with the proxy, we select the file and click “Upload”:



Here Burp Suite will intercept the file upload request:

To better handle the request and be able to analyze the server’s response more effectively, we’re going to send the request to the repeater with Ctrl R.
Once sent, we click “Send” to see the server’s response to the default request:

It tells us that PHP files are not allowed. So the idea is going to be to try alternatives to the PHP extension to see if they aren’t defined in the blacklist. On Wikipedia, we can see the types of extensions associated with PHP:

Having said that, we send the request from the repeater to the intruder by pressing Ctrl I. Once we have the request in the intruder, we’ll click the clear button to remove the substitution positions that are set by default:

Since what we’re interested in is launching several requests where the only difference between each one is the extension, we’ll declare a substitution field in the file name extension:

With this done, we’ll go to the “Payloads” tab:

Once here, we’ll define our dictionary, that is, the dictionary that will be used to substitute the default extension with those defined in the dictionary:


Once we have the dictionary of extensions to test ready, we’ll go to the “Options” tab and the “Grep - Extract” section:

Once here, we’ll set the string we want it to filter by in the different responses, so that when it doesn’t have the indicated string, we can quickly detect the response where it’s not present:

Once done, we’ll go back to the “Payloads” tab to start the attack:


A new window will open regarding the attack:

In this case, as we can see, it seems that the only extension the server doesn’t allow is PHP. So presumably all the others have been uploaded. Let’s view the response to the last request in the browser, to do this we do the following:




Once we have the response, we can disable Burp Suite because we won’t use it anymore:

With this done, we return to our profile:

Now, if we look at the profile, we can see how the avatar has changed, and now shows an error that the image doesn’t load properly:

By right-clicking, we can go to the direct path of the image to see if it’s our PHP file:


Watch out, the file seems to exist because it doesn’t give us a 404 error, however, it’s not fully interpreted since it hasn’t read the file we indicated it should read. No problem, before panicking let’s try with the other files with another extension we uploaded, for example, phtml:

This one does interpret it for us, and in this way we manage to read the secret file.
Having read it, we simply submit the solution:


And in this way, we complete the lab:


Although we’ve solved it this way, PortSwigger’s solution seems really cool and important to comment on:
- We log in and upload an image of our avatar, with this done, we return to our profile page.
- In Burp Suite, we go to
Proxy > HTTP History. Here we’ll be able to see a GET request to the/files/avatars/<file>path. We send this response to the repeater. - On our system, we create a file called
exploit.phpthat contains code to read the contents of Carlos’s secret file. For example:<?php echo file_get_contents('/home/carlos/secret'); ?> - We try to upload this file as our avatar. The server’s response will indicate that PHP extension files are not allowed.
- In the HTTP History, we’ll now look for the POST request where we tried to upload the PHP file. In the server’s response to this request, we’ll be able to realize that we’re dealing with an Apache server. Having said that, we send this request to the repeater.
- In the POST request that we now have in the repeater, we’re going to make the following changes:
- We change the file name to
.htaccess. - We change the
Content-Typevalue totext/plain - We replace the file content (the PHP code) with the following Apache directive:
AddType application/x-httpd-php .l33tThis directive will add a new extension to the server, additionally, indicating that the MIME type isapplication/x-httpd-php, which means it will behave like a PHP file. Since the server usesmod_php(PHP module for Apache), it will know and understand what we’re telling it.
- We change the file name to
- We send the request, and we’ll see that the server will indicate in the response that the file has been uploaded successfully.
- Now we return to the original PHP file request, and the only thing we’ll change is the name. We’ll change
exploit.phpto, for example,exploit.l33t. With this, we send the request and we’ll see it has been uploaded successfully. - Now, returning to the GET request of
/files/avatars/<file>where file will beexploit.l33t, when we make it, the response will return Carlos’s secret. - We submit the solution and lab completed.