Home > Backend Development > PHP Tutorial > How to prevent users from uploading PHP executable files (with examples)

How to prevent users from uploading PHP executable files (with examples)

不言
Release: 2023-04-04 19:02:02
forward
4060 people have browsed it

The content of this article is about how to prevent users from uploading PHP executable files. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

Every professional PHP developer knows that user-uploaded files are extremely dangerous. Both back-end and front-end hackers can use them to cause trouble.

About a month ago, I read an article on reddit about PHP upload vulnerability detection, so I decided to write an article. User darpernter asked a tough question:

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

The top reply is:

If the file suffix Change it to .txt, then it will not be executed as a php file, so you can rest assured. But make sure again that it is not uploaded with the .php.txt suffix.

Sorry, the correct answer to the question is not. Although the above answers are not all wrong, they are obviously not comprehensive. Surprisingly, most of the answers were very similar.

I want to explain this issue clearly. So what I was going to talk about got a little big and I decided to make it even bigger.

Problem

People allow users to upload files, but are worried about the files uploaded by users being executed on the server.

Start by looking at how the php file is executed. Assuming a server with a php environment, then it usually has two methods to execute php files externally. One is to directly request the file using URL, like http://example.com/somefile.php. The second one, which is commonly used by PHP nowadays, is to forward all requests to index.php and somehow introduce other files in this file. Therefore, there are two ways to run code from a php file: execute the file or use the include/include_once/require/require_once method to introduce other files that need to be run.

In fact, there is a third method: 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 very dangerous, but if you use it, it usually means that you confirm that you are doing something dangerous and that you have no other options. Actually, eval() has its uses and can be very useful in some situations. But if you are a newbie, I don't recommend you to use it. See this article at OWASP. I wrote a lot on it.

So, there are two ways to execute the code in the file: execute it directly or introduce it in the file being executed. So how to avoid this happening?

Solution?

How can we know that a file contains php code? Look at the extension name. If it ends with .php, like somefile.php, we think it contains PHP code.

If there is a somefile.php file in the root directory of the website, then access http://example.com/somefile.php in the browser, this file will be executed and the content will be output to the browser.

But what happens if I rename this file? What if I rename it to somefile.txt or somefile.jpg? What will I get? I'll get its contents. It will not be executed. It will be sent directly from the hard disk (or cache).

The answer on the reddit community is correct on this point. Renaming prevents a file from being executed unexpectedly, so why do I think this solution is wrong?

I believe you noticed the question mark I added after "solution". This question mark makes sense. Nowadays it is almost impossible to see a separate php file on the URL of most websites. And even if there is, it is deliberately forged because .php is required on the URL to achieve backward compatibility with older versions of URLs.

Now most of the php code is introduced at runtime, because all requests are sent to index.php in the root directory of the website. This file will import other php files according to specific rules. Such rules can (or will in the future) be used maliciously. If your application's rules allow the introduction of user files, the application will be vulnerable to attack and you should take immediate steps to prevent the user's files from being executed.

How to prevent the introduction of files uploaded by users?

Is it okay to rename the file? ---  No, it can’t be done!

The PHP parser does not care about the file extension. In fact, all programs don't care. Double-click the file and the file will be opened by the corresponding program. The file extension simply helps the operating system identify what program to use to open the file. A program can open any file as long as the program has the ability to read the file. Sometimes a program refuses to open and operate a file. But that's not because of the suffix, it's because of the file content.

The server is usually set up to execute the .php file and output the execution results back. If you request the image .jpg --- it will be returned unchanged from disk. What happens if you ask the server to run a jpeg image in a certain way? Will the server execute or not?

The program does not care about the file name. It doesn't even matter if the file has a name, or if it is a file at all.

What is required to execute PHP code from a file?

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

  1. The code is between &lt ;?php and ?> tags

  2. Code between = and ?&gt ; Between tags

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

How to prevent users from uploading PHP executable files (with examples)

There is nothing wrong with the image

It is pure now. But you probably know that the JPEG format allows some comments to be added to the file. For example, the camera model or coordinate address that took the photo. What if we try to put some PHP code in there and try to include or require ? Let’s take a look!

Question! 1

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

Create a file using the following PHP code:

<h1>Problem?</h1>
<img  alt="How to prevent users from uploading PHP executable files (with examples)" >
<?php include "./troll-face.jpg";
Copy after login
  1. Save a picture named troll-face.jpg

  2. Put the pictures and php script files in the same folder

  3. Open the browser to request the php file

If you name your php file index.php, then put it in the file root directory or any file directory under your website directory.

If you complete the above steps accurately, you can see this screen:

How to prevent users from uploading PHP executable files (with examples)

Nothing is wrong so far. No PHP code is displayed, and no PHP code is executed.

Now, let's add a question:

  1. Open the file properties dialog or run some application that allows editing EXIF ​​information

  2. Switch to the Details tab or otherwise edit the information

  3. Scroll down to the camera parameters

  4. Copy the code below to After the "camera maker" field:

<?php  echo "<h2>Yep, a problem!"; phpinfo(); ?>
Copy after login

How to prevent users from uploading PHP executable files (with examples)

Refresh the page!

How to prevent users from uploading PHP executable files (with examples)

Clearly something went wrong!

You see this image on the page. The same image also exists in the PHP code of the page. The code for the image is also executed.

What should we do?!! 1

Long story short: If we don't include these unsafe files in the program, the scripts in the files will not be executed.

Look carefully at the example below.

Final answer?

If anyone sees somewhere that I'm wrong - please correct me, this is a serious issue.

PHP is a scripting language. You always need to reference some file that combines paths dynamically. Therefore, to protect your server, you must check paths 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 path to the file before using Upload or Create. If it's in a folder your application script allows - then it can include this file using include_once or require or require_once. If not - then don't introduce it.

How to check? this is very simple. You just need to compare the $folder (file) path to a path folder that allows the program to import files ( $file ).

// 不好的例子,不要用!
if (substr($file, 0, strlen($folder)) === $folder) {
  include $file;
}
Copy after login

If the storage path of $folder is /path/to/folder and the storage path of $file is /path /to/folder/and/file, then we use the substr() function in the code to turn their paths into negative strings for judgment. If the files are located in different folders---this string will not equal. The opposite is true.

上面的代码有两个重要的问题。如果 file 路径是  /path/to/folderABC/and/file,很明显,该文件也不在允许引入的文件夹中。通过向两个路径添加斜杠可以防止这种情况。我们在这里向文件路径添加斜杠并不重要,因为我们只需要比较两个字符串。

举个例子: 如果 folder 路径是  /path/to/folder  并且 file 路径是 /path/to/folder/and/file ,那么从 file 提取和 folder 具有相同数量的字符,那么 $ folder 将是 /path/to/folder

再比如 folder 路径是 /path/to/folder 并且 file 路径是 /path/to/folderABC/and/file, 那么从 file 中提取 folder 具有相同数量的字符,和 $folder一样,并且将再次成为/path/to/folder,这种都是错误的,这不是我们期望的结果。

因此,在 /path/to/folder/ 添加斜杠后,与 /path/to/folder/and/file 的提取部分 /path/to/folder/ 相同就是安全的。

如果将 /path/to/folder//path/to/folderABC/and/file 的提取部分 / path/to/folderA ,很明显二个字符串不一样。

这就是我们期望得到的。但还有另一个问题。这并不明显。我敢肯定,如果我问你,你看到这里有一个灾难性的漏洞 - 你不会猜到它在哪里。你也许已经在经验中使用过这个东西,甚至可能就在今天。现在,您将看到漏洞是如何隐晦和显而易见。往下看。

/../

假想一个很常见的场景。

有这么一个网站。用户可以上传文件到该站点。所有的文件都位于一个特定的目录下。有一个包含用户文件的脚本。脚本自上而下进行查找是否包含用户的输入(直接或间接)路径---那这个脚本可以通过如下方式进行路径伪造:

/path/to/folder/../../../../../../../another/path/from/root/
Copy after login

举例。用户发起请求,你的脚本中包含了一个基于类似如下用户输入路径的文件:

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

你麻烦大了。有天用户发送一个 ../../../../../../etc/.passwd 这种或其他请求,你就哭吧。

再不然。假如有人让你的脚本加载一个他想要的文件,你就废了。它不一定就只是出现在用户文件中。它可能是你的CMS或你自己文件的一些插件(别相信任何人),甚至是应用程序逻辑中的错误等。

或者

用户可能会上传一个名为 file.php 的文件,你会把它和其他的用户文件一样放在一个特定的文件夹里面:

move_uploaded_file($filename, $folder . '/' . $filename);
Copy after login

用户的文件就存放在那里,你必须常常检查从来没有包含该文件夹中的文件,目前来看,所有的东西都挺正常的。通常,用户发给你的文件不会包含斜杠或者其他特殊字符,因为这是被系统文件系统禁止的。之所以这样,是因为通常情况下浏览器发给你的文件是在真实文件系统中创建的,同时它的名字是一些真实存在的文件的名字。

但是 http 请求允许用户发送任何字符。所以如果某人伪造请求创建名为 ../../../../../../var/www/yoursite.com/index.php 的文件---这行代码会覆盖你的 index.php 文件,如果 index.php 处于在上述路径的话。

所有的初学者都希望通过过滤 「..」或者斜杠来解决这个问题,但是这种做法是错误的,由于你在安全方面还缺乏经验。同时你必须(是的,必须)明白一个简单的事情:你永远无法在安全和密码学方面的获得足够的知识。这句话的意思是,如果你懂得了「两个点和斜杠」的漏洞,但这不代表你知道所有其他的缺陷、攻击和其他特殊字符,你也不知道在文件写入文件系统或数据库时可能发生的代码转换。

解决方案和答案

为了解决这个问题,PHP中内置了一些特殊函数方法,只是为了在这种情况下使用。

basename()

第一个解决方案 --- basename() 它从路径结束时提取路径的一部分,直到它遇到第一个斜杠,但忽略字符串末尾的斜杠,参见示例。无论如何,你会收到一个安全的文件名。如果你觉得安全 - 那么是的这很安全。如果它被不法上传利用 - 你可以使用它来校验文件名是否安全。

realpath()

另一个解决方案 --- realpath()它将上传文件路径转换规范化的绝对路径名,从根开始,并且根本不包含任何不安全因素。它甚至会将符号链接转换为此符号链接指向的路径。

因此,您可以使用这两个函数来检查上传文件的路径。要检查这个文件路径到底是否真正属于此文件夹路径。

我的代码

我编写了一个函数来提供如上的检查。我并不是专家,所以风险请自行承担。代码如下。

<?php /**
 * Example for the 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
 */
/**
 * 检查某个路径是否在指定文件夹内。若为真,返回此路径,否则返回 false。
 * @param String $path 被检查的路径
 * @param String $folder 文件夹的路径,$path 必须在此文件夹内
 * @return bool|string 失败返回 false,成功返回 $path
 *
 */
function checkPathIsInFolder($path, $folder) {
    if ($path === &#39;&#39; OR $path === null OR $path === false OR $folder === &#39;&#39; OR $folder === null OR $folder === false) {
      /* 不能使用 empty() 因为有可能像 "0" 这样的字符串也是有效的路径 */
        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)) {
        // 文件路径比文件夹路径短,那么这个文件不可能在此文件夹内。
        return false;
    }
    if (substr($pathRealpath, 0, strlen($folderRealpath)) !== $folderRealpath) {
        // 文件夹的路径不等于它必须位于的文件夹的路径。
        return false;
    }
    // OK
    return $path;
}
Copy after login

结语。

  • 必须过滤用户输入,文件名也属于用户输入,所以一定要检查文件名。记得使用 basename() 。

  • 必须检查你想存放用户文件的路径,永远不要将这个路径和应用目录混合在一起。文件路径必须由某个文件夹的字符串路径,以及 basename($filename) 组成。文件被写入之前,一定要检查最终组成的文件路径。

  • 在你引用某个文件前,必须检查路径,并且是严格检查。

  • 记得使用一些特殊的函数,因为你可能并不了解某些弱点或漏洞。

  • 并且,很明显,这与文件后缀或 mime-type 无关。JPEG 允许字符串存在于文件内,所以一张合法的 JPEG 图片能够同时包含合法的 PHP 脚本。

不要信任用户。不要信任浏览器。构建似乎所有人都在提交病毒的后端。

当然,也不必害怕,这其实比看起来的简单。只要记住 “不要信任用户”  以及 “有功能解决此问题” 便可。

The above is the detailed content of How to prevent users from uploading PHP executable files (with examples). For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:segmentfault.com
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template