Every professional PHP developer knows that files uploaded by users are extremely dangerous. Both backend and front-end hackers can take advantage of them.

About a month ago, I saw a PHP upload bug detection post on Reddit, so I decided to write a post about it. User Darpernter asked a tricky question:

Even though I renamed it ‘helloworld.txt’, will the attacker still be able to run his PHP script?

The top response was:

If the file suffix is changed to.txt, it will not be executed as a PHP file, so you can rest assured that it is not uploaded with the.php.txt suffix.

Unfortunately, that’s not the right answer to the question. While not all of the answers above are wrong, they are clearly incomplete. Surprisingly, most of the answers were very similar.

I want to clarify the problem. So the thing I was talking about got a little big, and I decided to make it bigger.

The problem

People allow users to upload files, but worry that the uploaded files will be executed on the server.

Start by looking at how PHP files are executed. Assuming a server with a PHP environment, it usually has two ways to execute PHP files externally. One is directly through the URL request file, like http://example.com/somefile.php. The second option, which PHP now uses, forwards all requests to index.php and somehow introduces other files in this file. So, there are two ways to run code from a PHP file: execute a file or introduce other files to run using the include/include_once/require/require_once methods.

There is a third method: the eval() function. It can execute the incoming string as PHP code. This function is used in most CMS systems to execute code stored in the database. The eval() function is dangerous, but if you use it, it usually means that you are confirming that you are doing something dangerous and that you have no other choice. In fact, eval() has its uses, and can be very useful in some cases. But if you’re new to it, I don’t recommend it. See this article at OWASP. I wrote a lot on it.

So, there are two ways to execute code in a file: directly or by importing it into the file being executed. So how to prevent this from happening?

The solution?

How do we know that a file contains PHP code? If you look at the extension, if something ends in.php, like somefile.php we assume that it has PHP code in it.

If under the web root directory is a somefile. PHP files, then visit http://example.com/somefile.php in the browser, the file will be executed and output to the browser.

But what happens if I rename this file? What if I rename it somefile.txt or somefile.jpg? What do I get? I’ll get the content of it. It will not be carried out. It is sent directly from the hard disk (or cache).

The Reddit community was right on this one. Renaming prevents a file from being executed unexpectedly, so why do I think this solution is wrong?

I’m sure you noticed the question mark I put after “solution”. The question mark makes sense. Most web sites today rarely see individual PHP files in their urls. And if it does, it’s a deliberate forgeries, because.php is required for backward compatibility with older versions of urls.

Most of the PHP code is now introduced at run time, because all requests are sent to index.php, the site’s root directory. This file introduces other PHP files according to specific rules. Such rules can (or will) be used maliciously. If your application rules allow users to import files, then your application is vulnerable and you should take immediate steps to prevent users’ files from being executed.

How do I prevent the import of files uploaded by users?

* Can I rename the file name? — * No, you can’t!

The PHP parser does not care about file name extensions. In fact, none of the programs care. Double-click the file, the file will be opened by the corresponding program. File suffixes simply help the operating system identify which program to open a file with. A program can open any file as long as it has the ability to read the file. Sometimes programs refuse to open and manipulate files. But that’s not because of the suffix, it’s because of the file.

The server is typically set up to execute the.php file and return the results to output. If you request picture.jpg – it will be returned from disk as is. What happens if you ask the server to run a JPEG image a certain way? Will the server execute or not?

Credit: Echo/Cultura/Getty Images

The program doesn’t care about file names. They don’t even care if the file has a name or if it is a file at all.

What does it take to execute PHP code from a file?

There are at least two cases where PHP can execute code:

  1. The code between<? php 和 ? >Mark between
  2. The code between<? = 和 ? >Mark between

Even if the file is filled with some strange binary data or some strange protection name, the code in this tag will still be executed.

Here is a picture for you:

There is no problem with this picture

It’s very pure now. But you probably know that the JPEG format allows you to add some comments to a file. For example, the model of the camera that took the picture or the location of the coordinates. What if we try to put some PHP code in there and try include or require? Let’s take a look!

The question! 1

Download this image to your hard drive. Or get yourself a JPEG. It doesn’t matter which format you use. I recommend using a JPEG file for the demonstration, mainly because it is an image and easy to text edit in. I use a Windows laptop and don’t currently have an Apple or Linux (or any other UNIX system) laptop on hand. So I’ll post a screenshot of the OS later. But I’m sure you can do it, too.

Create a file with this PHP code:

<h1>Problem? </h1> <img src="troll-face.jpg"> <? php include"./troll-face.jpg";
Copy the code
  1. Save an image namedtroll-face.jpg
  2. Put your images and PHP script files in the same folder
  3. Open your browser and request the PHP file

If you name your PHP file index.php, place it at the root of the file or in any file directory in your website directory.

If you complete the above steps correctly, you should see this screen:

There’s nothing wrong with that up to now. No PHP code is shown and no PHP code is executed.

Now, let’s add a question:

  1. Open the file properties dialog or run some application that allows you to edit EXIF information
  2. Switch to the Details TAB or otherwise edit the information
  3. Scroll down to camera parameters
  4. Copy the following code after the “Camera Maker” field:
<? phpecho "

Yep, a problem!

"
; phpinfo(); ? >Copy the code

Refresh the page!

Clearly there is a problem!

You see the image on the page. The same image also exists in the PAGE’s PHP code. The code for the image is also executed.

What do we do? !!!!! 1

Long story short: If we don’t introduce these insecure files into the program, the scripts in the files won’t execute.

Take a closer look at the following example.

The final answer?

If someone somewhere sees me wrong – please correct me, this is a serious problem.

PHP is a scripting language. You will always need to reference some dynamically combined path file. Therefore, to protect the server, you must check the path and prevent confusion between your site files and files uploaded or created by users. If the user’s files are separate from the application files, you can check the file path before using upload or create files. If it is in a folder that your application script allows – then it can import this file using either include_once or require or require_once. If not — then don’t introduce it.

How to check? That’s easy. You only need to compare the $Folder path to a path folder that allows applications to import files ($file).

// Don't use bad examples!if (substr($file, 0, strlen($folder= = =))$folder) {
  include $file;
}
Copy the code

If the $folder path is /path/to/folder and the $file path is /path/to/folder/and/file, then we use the substr() function to turn their paths into negative strings. If the files are in different folders – this string will not be equal. Vice versa.

There are two important problems with the code above. If the file path is/path/to/folderABC/and/file, it is obvious that the file is not allowed into the folder. You can prevent this by adding slashes to both paths. It doesn’t matter that we add slashes to the file path here, because we only need to compare two strings.

Here’s an example: If the folder path is /path/to/folder and the file path is /path/to/folder/and/file, then extract the same number of characters from file as the folder. Then the $folder will be /path/to/folder.

Again such as folder path/path/to/folder and the file path is/path/to/folderABC/and/file, then extracted from the file folder with the same number of characters, like $folder, And it will be the /path/to/folder again, which is all wrong, which is not what we want.

Therefore, it is safe to add a slash in /path/to/folder/ to be the same as the extracted part of /path/to/folder/and/file.

If the/path/to/folder/with/path/to/folderABC/and/file to extract some/path/to/folderA, obviously is not the same as the second string.

That’s what we expect. But there’s another problem. It’s not obvious. I’m sure if I asked you and you saw that there was a catastrophic leak here — you wouldn’t guess where it was. You’ve probably used this in your experience, maybe even today. Now you’ll see how subtle and obvious the vulnerability is. To look down.

/.. /

Imagine a very common scenario.

There’s a website. Users can upload files to the site. All files are located in a specific directory. There is a script that contains the user files. The script looks up to see if it contains the user’s input (direct or indirect) path — the script can do path forgery in the following way:

/path/to/folder/.. /.. /.. /.. /.. /.. /.. /another/path/from/root/Copy the code

For example. The user initiates the request, and your script contains a file based on the following user input path:

include $folder . "/" . $_GET['some']; // or $_POST, or whatever
Copy the code

You’re in big trouble. One day the user sent a.. /.. /.. /.. /.. /.. /etc/.passwd This or any other request, cry.

Or else. If someone tells your script to load a file they want, you’re useless. It doesn’t have to be just in the user file. It could be a plugin for your CMS or your own files (don’t trust anyone), or even a bug in your application logic.

or

The user might upload a file called file.php, which you put in a special folder like all other user files:

move_uploaded_file($filename.$folder . '/' . $filename);
Copy the code

That’s where the user’s files are stored, and you’ll often have to check for files that were never included in that folder. So far, everything is fine. Normally, the user will send you files that do not contain slashes or other special characters because they are prohibited by the system file system. The reason for this is that usually the file the browser sends you was created on a real file system and its name is the name of some real file.

But HTTP requests allow the user to send any character. So if someone forges a request to create a name called.. /.. /.. /.. /.. /.. / var/www/yoursite.com/index.php file – this line of code that will cover you index the PHP file, if the index in PHP in the path.

All beginners want to filter “..” Or slash to solve the problem, but this is a mistake because of your inexperience in security. And you must (yes, you must) understand one simple thing: you can never gain enough knowledge about security and cryptography. What this means is that if you know about the “two dots and slashes” vulnerability, that doesn’t mean you know about all the other bugs, attacks, and other special characters, nor do you know about the code-switching that can happen when a file is written to a file system or database.

Solutions and answers

To solve this problem, PHP has some special function methods built in, just for use in this case.

basename()

The first solution, basename(), extracts a portion of the path from the end of the path until it encounters the first slash, but ignores the slash at the end of the string, as shown in the example. In any case, you will receive a secure filename. If you think it’s safe. – Then yes it’s safe. If it is exploited by illegal uploads – you can use it to verify that the file name is secure.

realpath()

Another solution, realPath (), converts the uploaded file path to a normalized absolute path name, starting at the root, and contains no unsafe elements at all. It even converts the symbolic link to the path that the symbolic link points to.

Therefore, you can use these two functions to check the path of the uploaded file. Check that the file path really belongs to the folder path.

My code

I wrote a function to provide the above checks. I’m not an expert, so do it at your own risk. Here’s the code.

<? php /** * Exampleforthe article at medium.com * Created by Igor Data. * User: igordata * Date: 2017-01-23 * @link https://medium.com/@igordata/php-running-jpg-as-php-or-how-to-prevent-execution-of-user-uploaded-files-6ff021897389 Read The article */ /** * checks whether a path is in the specified folder. Return this path if true, otherwisefalse. * @param String$pathChecked path * @param String$folderThe path to the folder,$pathMust be in this folder * @returnBool | return string failurefalse, returns successfully$path* * /function checkPathIsInFolder($path.$folder) {
    if ($path= = =' ' OR $path === null OR $path= = =false OR $folder= = =' ' OR $folder === null OR $folder= = =false) {/* Do not use empty() because it might look like"0"Such a string is also a valid path */return false;
    }
    $folderRealpath = realpath($folder);
    $pathRealpath = realpath($path);
    if ($pathRealpath= = =false OR $folderRealpath= = =false) {
        // Some of paths is empty
        return false;
    }
    $folderRealpath = rtrim($folderRealpath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
    $pathRealpath = rtrim($pathRealpath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
    if (strlen($pathRealpath) < strlen($folderRealpath) {// The file path is shorter than the folder path, so the file cannot be in the folder.return false;
    }
    if (substr($pathRealpath, 0, strlen($folderRealpath))! = =$folderRealpath) {// The path of a folder is not equal to the path of the folder it must be in.return false;
    }
    // OK
    return $path;
}
Copy the code

The epilogue.

  • User input must be filtered, and file names belong to user input, so be sure to check file names. Remember basename().
  • Always check where you want to store user files and never mix this path with the application directory. The file path must be the string path of a folder, as wellbasename($filename)Composition. Before a file is written, be sure to check the path of the final composed file.
  • Before you reference a file, you must check the path, and strictly.
  • Remember to use special functions because you may not be aware of weaknesses or vulnerabilities.
  • And, obviously, it has nothing to do with file suffixes or MIME-types. JPEG allows strings to exist in a file, so a valid JPEG image can also contain a valid PHP script.

Don’t trust users. Don’t trust browsers. Build a back end where everyone seems to be submitting viruses.

Of course, don’t be afraid; it’s easier than it looks. Just remember “do not trust users” and “there is a feature to fix this”.

From PHP/Laravel developer community laravel-china.org/topics/1962…