0

To protect the websites we're programming from attacks such as SQL-Injection or XSS we need to filter users' inputs before storing or displaying it.

In PHP, we use htmlspecialchars and addslashes functions to the inputs to prevent XSS and SQL-Injection attacks. So, what about the files ?

I used to protect web-apps by checking the files type and it's extension to know if those were in the whitelist or not. But I don't use htmlspecialchars and addslashes functions because I didn't see someone using this approach.

For example, if I want to get the file name I use $_FILES['file']['tmp_name'] then I store it directly to the database.

Is this wrong or it cannot be injected with codes, commands ... etc.

Machavity
  • 30,841
  • 27
  • 92
  • 100
Ghadeer
  • 48
  • 4
  • 1
    `In PHP, we use htmlspecialchars and addslashes functions to the inputs to prevent XSS and SQL-Injection attacks` .... This isn't sufficient. PHP *does* have some built-in filtering options you should look into. – GetSet Jul 04 '20 at 04:05
  • Its a good question tho on the filtering of the `$_FILES` elements. I dont know off hand. – GetSet Jul 04 '20 at 04:07
  • `$_FILES['file']['tmp_name']` is the name of the temporary file on the server, it is **not** the name of the ile as passed to the server from the client machine. This file does not have a file-type indicated by a dot and then some alhpanumeric characters (such as `.pdf`, etc.). – Martin Jul 05 '20 at 20:35
  • @Martin you're right, but the $_FILES is an array that includes some variables (not only 'tmp_name'). For example, you have the variable 'name' which is the filename came from the machine which can be injected also! – Ghadeer Jul 05 '20 at 20:41
  • @Ghadeer but you specfically referenced the `tmp_name` value in your text. If this is incorrect then you should edit and update your text – Martin Jul 05 '20 at 20:43
  • "Dear" is for addressing somebody you know intimately, in an intimate and informal setting. Please don't address other people this way on Stack Overflow, it is *not* appropriate, and comes across as either condescending or misogynistic. While we're on the topic, "bro" isn't appropriate for Stack Overflow either. – user229044 Jul 05 '20 at 22:37
  • 1
    Please also don't include the answer in your question. If you have an answer to provide, post it below as an answer, and **do not** summarize other people's answers into your own. That's not how Stack Overflow works. – user229044 Jul 05 '20 at 22:39
  • After some thought, this is too broad. There are many, many ways your application may be vulnerable, depending mostly on your use of `$_FILES` and your specific requirements. You should narrow your question down to a particular usecase with some code demonstrating your proposed use. – user229044 Jul 06 '20 at 02:03

3 Answers3

3

Do I need to filter the $_FILES['file'] before dealing with it?

Short answer: NO. It's a bunch of string values, nothing more.

Long Answer:

I used to protect web-apps by checking the files type and it's extension to know if those were in the whitelist or not.

This is a good approach, if applied and carried out correctly.

the $_FILES array is simply a carrier. It can't itself be abused, but you have to trust what it carries - ie trusting the file that is being passed to/by the server.


While I write this answer; below; it seems the OP is confused about what they're actually protecting against and why:

The OP states as "best practise" (which it is absolutely not):

If you want to use $_FILES['file']['tmp_name'] to be stored into your database or to display in your UI, you should use addslashes or PDO prepare statement to be protected against SQL-Injection attacks.

This is a misunderstanding of how the $_FILES array is populated. The $_FILES['file']['tmp_name'] value is set by the server, not by the user or the client.

The user given values are:

$_FILES['file']['name']
$_FILES['file']['type']
$_FILES['file']['size']

These are the string values that would need to be vetted. As long as you do not trust these string values, you have nothing to worry about.


Storing files inside your database is not usually a good idea and has its own pitfalls, dhnwebpro has their own answer on this question, regarding database safety.


$_FILES['file']['tmp_name'] is the server location of the file in temporary storage space.

The PHP Manual clearly states:

Files will, by default be stored in the server's default temporary directory, unless another location has been given with the upload_tmp_dir directive in php.ini. The server's default directory can be changed by setting the environment variable TMPDIR in the environment in which PHP runs.

The file will be deleted from the temporary directory at the end of the request if it has not been moved away or renamed.

If you think that your $_FILES['file']['tmp_name'] value is being abused then this is a sign of server compromise and you've got a whole lorry load of trouble on your plate, well beyond a nefarious file upload.


So, how to vet the file that is being carried?

There are numerous types of file attacks and this topic is far beyond the scope that you are asking. For example; a genuine JPEG image can contain XSS scripting in the JPEG Metadata, but this XSS is triggered when the JPEG is loaded and viewed, but for all intents and purposes the JPEG file is not a "bad file" or is not a XSS file, to an outside observer who doesn't specifically check for this vulnerability.

So, do you block this file.jpg or do you block all Jpeg files? It's a tough call to make but in PHP there are some very good work arounds (which are also I believe out of scope for this question). In short; your question could do with some editing and clarity as to what exactly you're trying to protect against and how far you're willing to go to reach that level of protection.

I can give you a rough catch-all guide to preventing certain MIME file-types from being accepted onto your server. This looks and feels like what you want, something to stop sneaky MP4 videos being uploaded as document files (or vice versa).

1:

Ignore the filename ($_FILES['file']['name']). NEVER trust user data.

Edit: As pointed out by meagar, you may need to retain the original filename, in which case you should check it with a REGEX or similar to remove unwanted characters...

2:

Ignore the declared filetype ($_FILES['file']['type']). Any filename given MIME type (such as .pdf) should simply be ignored. NEVER trust user data.

3:

Use the PHP Finfo function set as a preliminary indicator. It is not perfect but will catch most things.

$finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
$mimeType = finfo_file($finfo, $_FILES['file']['tmp_name']);
$whitelist = ['text/html','image/gif','application/vnd.ms-excel'];
finfo_close($finfo);
if(in_array($mimeType,$whitelist)){
    // File type is acceptable.
}

4: Images:

If you are checking an uploaded image the best approach is to check the finfo filetype as per 3 and then have PHP load the image into a blank canvas and resave the image, thereby stripping out all the excess metadata and other potentially undesirable data that is not image-data.

Like this method: Remove exif data from jpg using php.

5:

It is also advisable always to give your uploaded files randomised names, never using the $_FILES['file']['name'] value.

6:

Depending on the sort of threat you're trying to avoid and/or neutralise, you can open the uploaded file and read the first few bytes of the file and compare it with confirmed bytes from whitelisted files of that type. This is quite nuanced and again beyond the scope of this answer, which is quite long enough.

Martin
  • 22,212
  • 11
  • 70
  • 132
  • 1
    All sound advice, but ignoring the user-supplied filename is not always possible, depending on your UX. In many cases, you want to preserve the original filename for the user. – user229044 Jul 05 '20 at 22:53
  • @meagar yes, then it should be checked using a regex or similar. I will edit. Cheers – Martin Jul 05 '20 at 23:21
1

There is a function is_uploaded_file to determine that the file is indeed an uploaded file and not some sort of file path manipulation on the user's part. As far as I know, there is no way that is_uploaded_file($_FILES['file']['tmp_name']) will return false. You should also check that the filesize($_FILES['file']['tmp_name']) is less than the size of the column you are inserting into.

As for "storing it directly to the database" you still need practice good SQL injection prevention on the file contents. Additionally, it is usually difficult to scale a solution that stores files in a database, but that is a separate issue that you have presumably considered already.

drussey
  • 665
  • 4
  • 9
  • 1
    `$_FILES['file']['size']` can be passed by the client and should not be trusted. This value can also be unreliable when working with blob data, as *exact* filesize values can depend on the OS that is reading the file. If the file has uploaded OK to the server it's better to read the size of the file at rest on the server, rather than depend on 3rd hand information.... – Martin Jul 05 '20 at 23:31
  • Thanks, I have edited my response accordingly. – drussey Jul 06 '20 at 14:11
-1

If you're using PDO or MySQLi you should be able to place the file in a prepared statement which should protect you from SQL injection attacks. I pasted a method from https://www.mysqltutorial.org/php-mysql-blob/ which has some good information on storing files in a MySQL database.

/**
 * insert blob into the files table
 * @param string $filePath
 * @param string $mime mimetype
 * @return bool
 */
public function insertBlob($filePath, $mime) {
    $blob = fopen($filePath, 'rb');

    $sql = "INSERT INTO files(mime,data) VALUES(:mime,:data)";
    $stmt = $this->pdo->prepare($sql);

    $stmt->bindParam(':mime', $mime);
    $stmt->bindParam(':data', $blob, PDO::PARAM_LOB);

    return $stmt->execute();
}

Or you could store the file on the filesystem and just include a reference to the file for when you need to serve it up. This method is quicker, but doesn't have the convenience of keeping all your data in one place.

The details about the elements of the $_FILES array are kind of buried in the manual, but they can be found at the end of example 1 here:

https://www.php.net/manual/en/features.file-upload.post-method.php

The values on all elements of the $_FILES array should be regarded as user input. I would recommend ignoring those values. However, if you wish to write them in to a database and/or display them later in your UI, you definitely need to protect yourself from SQL injection and XSS attacks. So using prepared statements and htmlspecialchars wouldn't hurt, in that case.

dhnwebpro
  • 134
  • 4
  • Thanks for your reply, but I am not talking about how to prepare a parameter to pass into a query for storing in the database and how to protect it for SQL-Injection. My question was about do I need to `prepare` and `htmlspecialchars` the variables of `$_FILES` array before storing it or not ? – Ghadeer Jul 04 '20 at 14:16
  • If you're just worried about the variables of `$_FILES` and not the file itself then a prepared statement will protect you from SQL injection. I usually get my own values for file name and mime type so I don't look at those as those come from the user or their browser. So it probably wouldn't hurt to use `htmlspecialchars` on those if you're not going to roll your own as I suppose they could include XSS code. – dhnwebpro Jul 05 '20 at 08:51
  • Yes, you're right. The solution is to prepare and use `htmlspecialchars` in the same time to protect this array also from attacks like SQL-Injection and XSS. Please add these details to your answer to set the question as answered. – Ghadeer Jul 05 '20 at 16:34
  • I note your posted method has no guide as to how to show the real MIME type of the file..... `:-/` – Martin Jul 05 '20 at 20:44
  • @Martin this protection is known by all. There's no need to repeat it again here in this question. The question as about "What to do if the attacker has injected the `$_FILES` array with a code instead of a file. Understand the question first ;) – Ghadeer Jul 05 '20 at 21:10
  • @Ghadeer , how do you know the file type? That's a bold statement. – Martin Jul 05 '20 at 21:14
  • 2
    `htmlspecialchars` is for making arbitrary strings safe to display in a web browser. It has nothing to do with databases. You should escape the data for the context, not preemptively escape it for every potential future use. Don't use `htmlspecialchars` until you read the data *from* the database and send it to a browser. – user229044 Jul 05 '20 at 22:49
  • @Martin There are several reliable (sort of) ways for finding the MIME type of a file in PHP. I was focusing on the DB read/write here. But you can use `mime_content_type()` or `finfo()` just to name a couple. Here's an answer from SO with more on MIME types. [link](https://stackoverflow.com/questions/21906596/find-mime-type-of-file-or-url-using-php-for-all-file-format) You would also do well to make a list of allowed types to prohibit types you don't wish to deal with. – dhnwebpro Jul 07 '20 at 04:12
  • @dhnwebpro you seem to have confused me with someone else? I have referenced almost exactly what you've posted in my own answer! I even reference your answer for guidance on how to deal with database interactions! Yes, there are several ways of finding the correct mime type in "most" cases, but the OP referenced that this was "known by all" which is sadly untrue. Whilst there are many ways of detecting a file mime type, using the `$_FILE` given data is not one of the better ones. – Martin Jul 07 '20 at 11:54