Bypassing Filters
Last updated
Last updated
To bypass these protections, we can either modify the upload request to the back-end server
, or we can manipulate the front-end code to disable these type validations
.
Let's start by examining a normal request through Burp
. When we select an image, we see that it gets reflected as our profile image, and when we click on Upload
, our profile image gets updated and persists through refreshes. This indicates that our image was uploaded to the server, which is now displaying it back to us
If we capture the upload request with Burp
, we see the following request being sent by the web application:
The web application appears to be sending a standard HTTP upload request to /upload.php
. This way, we can now modify this request to meet our needs without having the front-end type validation restrictions. If the back-end server does not validate the uploaded file type, then we should theoretically be able to send any file type/content, and it would be uploaded to the server.
The two important parts in the request are filename="HTB.png"
and the file content at the end of the request. If we modify the filename
to shell.php
and modify the content to the web shell we used in the previous section; we would be uploading a PHP
web shell instead of an image.
So, let's capture another image upload request, and then modify it accordingly:
We may also modify the Content-Type
of the uploaded file, though this should not play an important role at this stage, so we'll keep it unmodified.
To start, we can click [CTRL+SHIFT+C
] to toggle the browser's Page Inspector
, and then click on the profile image, which is where we trigger the file selector for the upload form:
This will highlight the following HTML file input on line 18
:
Here, we see that the file input specifies (.jpg,.jpeg,.png
) as the allowed file types within the file selection dialog. However, we can easily modify this and select All Files
as we did before, so it is unnecessary to change this part of the page.
The more interesting part is onchange="checkFile(this)"
, which appears to run a JavaScript code whenever we select a file, which appears to be doing the file type validation. To get the details of this function, we can go to the browser's Console
by clicking [CTRL+SHIFT+K
], and then we can type the function's name (checkFile
) to get its details:
The key thing we take from this function is where it checks whether the file extension is an image, and if it is not, it prints the error message we saw earlier (Only images are allowed!
) and disables the Upload
button. We can add PHP
as one of the allowed extensions or modify the function to remove the extension check.
Luckily, we do not need to get into writing and modifying JavaScript code. We can remove this function from the HTML code since its primary use appears to be file type validation, and removing it should not break anything.
To do so, we can go back to our inspector, click on the profile image again, double-click on the function name (checkFile
) on line 18
, and delete it
You may also do the same to remove accept=".jpg,.jpeg,.png"
, which should make selecting the PHP
shell easier in the file selection dialog, though this is not mandatory, as mentioned earlier.
Once we upload our web shell using either of the above methods and then refresh the page, we can use the Page Inspector
once more with [CTRL+SHIFT+C
], click on the profile image, and we should see the URL of our uploaded web shell:
As we can see, our attack did not succeed this time, as we got Extension not allowed
. This indicates that the web application may have some form of file type validation on the back-end, in addition to the front-end validations.
There are generally two common forms of validating a file extension on the back-end:
Testing against a blacklist
of types
Testing against a whitelist
of types
Furthermore, the validation may also check the file type
or the file content
for type matching. The weakest form of validation amongst these is testing the file extension against a blacklist of extension
to determine whether the upload request should be blocked. For example, the following piece of code checks if the uploaded file extension is PHP
and drops the request if it is:
The code is taking the file extension ($extension
) from the uploaded file name ($fileName
) and then comparing it against a list of blacklisted extensions ($blacklist
). However, this validation method has a major flaw. It is not comprehensive
, as many other extensions are not included in this list, which may still be used to execute PHP code on the back-end server if uploaded.
The comparison above is also case-sensitive, and is only considering lowercase extensions. In Windows Servers, file names are case insensitive, so we may try uploading a php
with a mixed-case (e.g. pHp
), which may bypass the blacklist as well, and should still execute as a PHP script.
There are many lists of extensions we can utilize in our fuzzing scan. PayloadsAllTheThings
provides lists of extensions for PHP and .NET web applications. We may also use SecLists
list of common Web Extensions.
We may use any of the above lists for our fuzzing scan. As we are testing a PHP application, we will download and use the above PHP list. Then, from Burp History
, we can locate our last request to /upload.php
, right-click on it, and select Send to Intruder
. From the Positions
tab, we can Clear
any automatically set positions, and then select the .php
extension in filename="HTB.php"
and click the Add
button to add it as a fuzzing position:
We'll keep the file content for this attack, as we are only interested in fuzzing file extensions. Finally, we can Load
the PHP extensions list from above in the Payloads
tab under Payload Options
. We will also un-tick the URL Encoding
option to avoid encoding the (.
) before the file extension. Once this is done, we can click on Start Attack
to start fuzzing for file extensions that are not blacklisted:
We can sort the results by Length
, and we will see that all requests with the Content-Length (229
) passed the extension validation, as they all responded with File successfully uploaded
. In contrast, the rest responded with an error message saying Extension not allowed
.
Now, we can try uploading a file using any of the allowed extensions
from above, and some of them may allow us to execute PHP code. Not all extensions will work with all web server configurations
, so we may need to try several extensions to get one that successfully executes PHP code.
Let's use the .phtml
extension, which PHP web servers often allow for code execution rights. We can right-click on its request in the Intruder results and select Send to Repeater
. Now, all we have to do is repeat what we have done in the previous two sections by changing the file name to use the .phtml
extension and changing the content to that of a PHP web shell
The following is an example of a file extension whitelist test:
We see that the script uses a Regular Expression (regex
) to test whether the filename contains any whitelisted image extensions. The issue here lies within the regex
, as it only checks whether the file name contains
the extension and not if it actually ends
with it. Many developers make such mistakes due to a weak understanding of regex patterns.
So, let's see how we can bypass these tests to upload PHP scripts.
For example, if the .jpg
extension was allowed, we can add it in our uploaded file name and still end our filename with .php
(e.g. shell.jpg.php
), in which case we should be able to pass the whitelist test, while still uploading a PHP script that can execute PHP code.
However, this may not always work, as some web applications may use a strict regex
pattern, as mentioned earlier, like the following:
This pattern should only consider the final file extension, as it uses (^.*\.
) to match everything up to the last (.
), and then uses ($
) at the end to only match extensions that end the file name. So, the above attack would not work
. Nevertheless, some exploitation techniques may allow us to bypass this pattern, but most rely on misconfigurations or outdated systems.
In some cases, the file upload functionality itself may not be vulnerable, but the web server configuration may lead to a vulnerability. For example, an organization may use an open-source web application, which has a file upload functionality. Even if the file upload functionality uses a strict regex pattern that only matches the final extension in the file name, the organization may use the insecure configurations for the web server.
For example, the /etc/apache2/mods-enabled/php7.4.conf
for the Apache2
web server may include the following configuration:
The above configuration is how the web server determines which files to allow PHP code execution. It specifies a whitelist with a regex pattern that matches .phar
, .php
, and .phtml
. However, this regex pattern can have the same mistake we saw earlier if we forget to end it with ($
). In such cases, any file that contains the above extensions will be allowed PHP code execution, even if it does not end with the PHP extension. For example, the file name (shell.php.jpg
) should pass the earlier whitelist test as it ends with (.jpg
), and it would be able to execute PHP code due to the above misconfiguration, as it contains (.php
) in its name.
Finally, let's discuss another method of bypassing a whitelist validation test through Character Injection
. We can inject several characters before or after the final extension to cause the web application to misinterpret the filename and execute the uploaded file as a PHP script.
The following are some of the characters we may try injecting:
%20
%0a
%00
%0d0a
/
.\
.
…
:
Each character has a specific use case that may trick the web application to misinterpret the file extension. For example, (shell.php%00.jpg
) works with PHP servers with version 5.X
or earlier, as it causes the PHP web server to end the file name after the (%00
), and store it as (shell.php
), while still passing the whitelist. The same may be used with web applications hosted on a Windows server by injecting a colon (:
) before the allowed file extension (e.g. shell.aspx:.jpg
), which should also write the file as (shell.aspx
). Similarly, each of the other characters has a use case that may allow us to upload a PHP script while bypassing the type validation test.
We can write a small bash script that generates all permutations of the file name, where the above characters would be injected before and after both the PHP
and JPG
extensions, as follows:
With this custom wordlist, we can run a fuzzing scan with Burp Intruder
, similar to the ones we did earlier. If either the back-end or the web server is outdated or has certain misconfigurations, some of the generated filenames may bypass the whitelist test and execute PHP code.
There are two common methods for validating the file content: Content-Type Header
or File Content
. Let's see how we can identify each filter and how to bypass both of them.
The following is an example of how a PHP web application tests the Content-Type header to validate the file type:
The code sets the ($type
) variable from the uploaded file's Content-Type
header. Our browsers automatically set the Content-Type header when selecting a file through the file selector dialog, usually derived from the file extension. However, since our browsers set this, this operation is a client-side operation, and we can manipulate it to change the perceived file type and potentially bypass the type filter.
We may start by fuzzing the Content-Type header with SecLists' Content-Type Wordlist through Burp Intruder, to see which types are allowed. However, the message tells us that only images are allowed, so we can limit our scan to image types, which reduces the wordlist to 45
types only (compared to around 700 originally). We can do so as follows:
The second and more common type of file content validation is testing the uploaded file's MIME-Type
. Multipurpose Internet Mail Extensions (MIME)
is an internet standard that determines the type of a file through its general format and bytes structure.
This is usually done by inspecting the first few bytes of the file's content, which contain the File Signature or Magic Bytes. For example, if a file starts with (GIF87a
or GIF89a
), this indicates that it is a GIF
image, while a file starting with plaintext is usually considered a Text
file. If we change the first bytes of any file to the GIF magic bytes, its MIME type would be changed to a GIF image, regardless of its remaining content or extension.
Many other image types have non-printable bytes for their file signatures, while a GIF
image starts with ASCII printable bytes (as shown above), so it is the easiest to imitate. Furthermore, as the string GIF8
is common between both GIF signatures, it is usually enough to imitate a GIF image.
Web servers can also utilize this standard to determine file types, which is usually more accurate than testing the file extension. The following example shows how a PHP web application can test the MIME type of an uploaded file:
Once we forward our request, we notice that we get the error message Only images are allowed
. Now, let's try to add GIF8
before our PHP code to try to imitate a GIF image while keeping our file extension as .php
, so it would execute PHP code regardless:
We can use a combination of the two methods discussed in this section, which may help us bypass some more robust content filters. For example, we can try using an Allowed MIME type with a disallowed Content-Type
, an Allowed MIME/Content-Type with a disallowed extension
, or a Disallowed MIME/Content-Type with an allowed extension
, and so on. Similarly, we can attempt other combinations and permutations to try to confuse the web server, and depending on the level of code security, we may be able to bypass various filters.