Maison >développement back-end >tutoriel php >Comment empêcher les utilisateurs de télécharger des fichiers exécutables PHP (avec exemples)

Comment empêcher les utilisateurs de télécharger des fichiers exécutables PHP (avec exemples)

不言
不言avant
2019-01-16 10:08:044098parcourir

Le contenu de cet article explique comment empêcher les utilisateurs de télécharger des fichiers exécutables PHP. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il vous sera utile.

Tous les développeurs PHP professionnels savent que les fichiers téléchargés par les utilisateurs sont extrêmement dangereux. Les pirates back-end et front-end peuvent les utiliser pour causer des problèmes.

Il y a environ un mois, j'ai lu un article sur la détection des vulnérabilités de téléchargement PHP sur Reddit, j'ai donc décidé d'écrire un article. L'utilisateur darpernter a posé une question difficile :

Même si je l'ai renommé 'helloworld.txt', l'attaquant pourra-t-il toujours exécuter son script php ?

La réponse principale est :

Si le suffixe du fichier change au format .txt, il ne sera pas exécuté en tant que fichier php, vous pouvez donc être assuré. Mais assurez-vous encore une fois qu'il n'est pas téléchargé avec le suffixe .php.txt.

Désolé, la bonne réponse à la question ne l'est pas. Bien que les réponses ci-dessus ne soient pas toutes fausses, elles ne sont évidemment pas exhaustives. Étonnamment, la plupart des réponses étaient très similaires.

Je veux expliquer ce problème clairement. Donc ce dont j'allais parler est devenu un peu grand et j'ai décidé de le rendre encore plus grand.

Problème

Les gens autorisent les utilisateurs à télécharger des fichiers, mais s'inquiètent de l'exécution des fichiers téléchargés par les utilisateurs sur le serveur.

Commencez par regarder comment le fichier php est exécuté. En supposant un serveur avec un environnement php, il dispose généralement de deux méthodes pour exécuter des fichiers php en externe. La première consiste à demander directement le fichier en utilisant une URL, comme http://example.com/somefile.php. La seconde, qui est couramment utilisée par PHP de nos jours, consiste à transmettre toutes les requêtes à index.php et à introduire d'une manière ou d'une autre d'autres fichiers dans ce fichier. Par conséquent, il existe deux manières d'exécuter du code à partir d'un fichier php : exécuter le fichier ou utiliser la méthode include/include_once/require/require_once pour introduire d'autres fichiers qui doivent être exécutés.

En fait, il existe une troisième méthode : la fonction eval(). Il peut exécuter la chaîne entrante sous forme de code php. Cette fonction est utilisée dans la plupart des systèmes CMS pour exécuter le code stocké dans la base de données. La fonction eval() est très dangereuse, mais si vous l'utilisez, cela signifie généralement que vous confirmez que vous faites quelque chose de dangereux et que vous n'avez pas d'autres options. En fait, eval() a son utilité et peut être très utile dans certaines situations. Mais si vous êtes débutant, je ne vous recommande pas de l'utiliser. Voir cet article sur OWASP. J'ai beaucoup écrit dessus.

Il existe donc deux manières d'exécuter le code dans le fichier : l'exécuter directement ou l'introduire dans le fichier en cours d'exécution. Alors, comment éviter que cela se produise ?

Solution ?

Comment peut-on savoir qu'un fichier contient du code php ? Regardez le nom de l'extension S'il se termine par .php, comme somefile.php, nous pensons qu'il contient du code php.

S'il y a un fichier somefile.php dans le répertoire racine du site Web, puis accédez à http://example.com/somefile.php dans le navigateur, ce fichier sera exécuté et le contenu sera affiché dans le navigateur.

Mais que se passe-t-il si je renomme ce fichier ? Et si je le renomme somefile.txt ou somefile.jpg ? Que vais-je obtenir ? Je vais récupérer son contenu. Il ne sera pas exécuté. Il sera envoyé directement depuis le disque dur (ou le cache).

La réponse sur Reddit est juste sur ce point. Renommer empêche un fichier d’être exécuté de manière inattendue, alors pourquoi est-ce que je pense que cette solution est fausse ?

Je crois que vous avez remarqué le point d'interrogation que j'ai ajouté après « solution ». Ce point d’interrogation est logique. De nos jours, il est presque impossible de voir un fichier php distinct sur l'URL de la plupart des sites Web. Et même s'il y en a, il est délibérément falsifié car .php est requis sur l'URL pour obtenir une compatibilité ascendante avec les anciennes versions des URL.

Désormais, la grande majorité du code php est introduite à la volée, car toutes les requêtes sont envoyées au répertoire racine du site Web index.php. Ce fichier importera d'autres fichiers php selon des règles spécifiques. De telles règles peuvent (ou seront à l’avenir) être utilisées de manière malveillante. Si les règles de votre application autorisent l'introduction de fichiers utilisateur, l'application sera vulnérable aux attaques et vous devez prendre des mesures immédiates pour empêcher l'exécution des fichiers utilisateur.

Comment empêcher l'introduction de fichiers téléchargés par les utilisateurs ?

Est-il possible de renommer le fichier ? ---Non, ce n’est pas possible !

L'analyseur PHP ne se soucie pas de l'extension du fichier. En fait, tous les programmes s’en moquent. Double-cliquez sur le fichier et le fichier sera ouvert par le programme correspondant. L'extension de fichier aide simplement le système d'exploitation à identifier le programme à utiliser pour ouvrir le fichier. Un programme peut ouvrir n'importe quel fichier tant qu'il a la capacité de lire le fichier. Parfois, un programme refuse d'ouvrir et d'exploiter un fichier. Mais ce n’est pas à cause du suffixe, c’est à cause du contenu du fichier.

Le serveur est généralement configuré pour exécuter .php fichiers et renvoyer les résultats de l'exécution. Si vous demandez une image .jpg --- elle sera renvoyée inchangée depuis le disque. Que se passe-t-il si vous demandez au serveur d'exécuter une image jpeg d'une certaine manière ? Le serveur s'exécutera-t-il ou non ?

Le programme ne se soucie pas des noms de fichiers. Peu importe que le fichier ait un nom ou même s'il s'agisse d'un fichier.

Que faut-il pour exécuter du code PHP à partir d'un fichier ?

Il existe au moins deux situations dans lesquelles PHP peut exécuter du code :

  1. Code entre <?php et Code entre les balises ?>

  2. Code entre les balises = et ?>

même si le fichier est rempli de des données binaires étranges ou un nom protégé étrange, le code dans la balise sera toujours exécuté.

Comment empêcher les utilisateurs de télécharger des fichiers exécutables PHP (avec exemples)

Il n'y a rien de mal avec l'image

Elle est pure maintenant. Mais vous savez probablement que le format JPEG permet d'ajouter certains commentaires au fichier. Par exemple, le modèle de l'appareil photo ou l'adresse des coordonnées qui a pris la photo. Et si nous essayions d'y mettre du code PHP et d'essayer d'inclure ou d'exiger ? Jetons un coup d'oeil !

Question 1

Téléchargez cette image sur votre disque dur. Ou vous pouvez obtenir vous-même une image JPEG. Peu importe le format de fichier que vous utilisez. Je recommande d'utiliser un fichier JPEG pour la démonstration, principalement parce qu'il s'agit d'une image et qu'il est facile d'y éditer du texte. J'utilise un ordinateur portable Windows et je n'ai actuellement pas d'ordinateur portable Apple ou Linux (ou autre système basé sur UNIX) sous la main. Je publierai donc une capture d'écran de ce système d'exploitation dans un instant. Mais je suis sûr que vous pouvez le faire aussi.

Créez un fichier en utilisant le code PHP suivant :

<h1>Problem?</h1>
<img  alt="Comment empêcher les utilisateurs de télécharger des fichiers exécutables PHP (avec exemples)" >
<?php include "./troll-face.jpg";
  1. Enregistrez une image et nommez-la troll-face.jpg

  2. Mettez les images et les fichiers de script php dans le même dossier

  3. Ouvrez le navigateur pour demander ce fichier php

Si vous mettez votre Le php Le fichier est nommé index.php, puis placez-le dans le répertoire racine du fichier ou dans n'importe quel répertoire de fichiers sous le répertoire de votre site Web.

Si vous effectuez les étapes ci-dessus avec précision, vous pouvez voir cet écran :

Comment empêcher les utilisateurs de télécharger des fichiers exécutables PHP (avec exemples)

Jusqu'à présent, tout va bien. Aucun code PHP n'est affiché et aucun code PHP n'est exécuté.

Maintenant, ajoutons une question :

  1. Ouvrez la boîte de dialogue des propriétés du fichier ou exécutez une application qui permet de modifier les informations EXIF ​​​​

  2. Passez à l'onglet Détails ou modifiez les informations

  3. Faites défiler jusqu'aux paramètres de la caméra

  4. Copiez le code ci-dessous pour Après le " champ "fabricant d'appareil photo" :

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

Comment empêcher les utilisateurs de télécharger des fichiers exécutables PHP (avec exemples)

Actualisez la page !

Comment empêcher les utilisateurs de télécharger des fichiers exécutables PHP (avec exemples)

De toute évidence, quelque chose s'est mal passé !

Vous voyez l'image sur la page. La même image existe également dans le code PHP de la page. Le code de l'image est également exécuté.

Que devons-nous faire ?!! 1

Pour faire court : si nous n'incluons pas ces fichiers dangereux dans le programme, les scripts contenus dans les fichiers ne seront pas exécutés.

Regardez attentivement l'exemple ci-dessous.

Réponse finale ?

Si quelqu'un voit quelque part que je me trompe, corrigez-moi, c'est un problème sérieux.

PHP est un langage de script. Vous devez toujours référencer un fichier qui combine dynamiquement les chemins. Par conséquent, pour protéger votre serveur, vous devez vérifier les chemins et éviter toute confusion entre les fichiers de votre site et les fichiers téléchargés ou créés par les utilisateurs. Si les fichiers de l'utilisateur sont distincts des fichiers de l'application, vous pouvez vérifier le chemin d'accès au fichier avant d'utiliser Télécharger ou Créer. S'il se trouve dans un dossier autorisé par votre script d'application, il peut alors inclure ce fichier en utilisant include_once ou require ou require_once. Si ce n'est pas le cas, ne le présentez pas.

Comment vérifier ? C'est très simple. Il vous suffit de comparer le chemin $folder (fichier) à un dossier de chemin qui permet au programme d'importer des fichiers ( $file ).

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

Si le chemin de stockage de $folder est /path/to/folder et que le chemin de stockage de $file est /path/to/folder/and/file , alors nous utilisons la fonction substr() dans le code pour transformer leurs chemins en mots Des chaînes négatives sont utilisées pour juger si les fichiers se trouvent dans des dossiers différents --- les chaînes ne seront pas égales. Le contraire est vrai.

上面的代码有两个重要的问题。如果 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/

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

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

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

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

或者

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

move_uploaded_file($filename, $folder . '/' . $filename);

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

但是 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;
}

结语。

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

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

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

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

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

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

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer