Heim > Artikel > Backend-Entwicklung > So verhindern Sie, dass Benutzer ausführbare PHP-Dateien hochladen (mit Beispielen)
In diesem Artikel geht es darum, wie man verhindert, dass Benutzer ausführbare PHP-Dateien hochladen. Freunde in Not können darauf verweisen.
Jeder professionelle PHP-Entwickler weiß, dass vom Benutzer hochgeladene Dateien äußerst gefährlich sind. Sowohl Back-End- als auch Front-End-Hacker können sie nutzen, um Ärger zu verursachen.
Vor etwa einem Monat habe ich auf reddit einen Artikel über die Erkennung von PHP-Upload-Schwachstellen gelesen, also habe ich beschlossen, einen Artikel zu schreiben. Benutzer darpernter hat eine schwierige Frage gestellt:
Auch wenn ich es in „helloworld.txt“ umbenannt habe, wird der Angreifer dann immer noch in der Lage sein, sein PHP-Skript auszuführen?Die oberste Antwort lautet:
Wenn sich das Dateisuffix ändert Wenn Sie es in .txt konvertieren, wird es nicht als PHP-Datei ausgeführt. Sie können also sicher sein, dass es nicht mit dem Suffix .php.txt hochgeladen wird.Tut mir leid, die richtige Antwort auf die Frage ist nicht. Obwohl die obigen Antworten nicht alle falsch sind, sind sie offensichtlich nicht vollständig. Überraschenderweise waren die meisten Antworten sehr ähnlich.
Ich möchte dieses Problem klar erklären. Also wurde das, worüber ich sprechen wollte, etwas zu groß und ich beschloss, es noch größer zu machen.
Problem
Benutzer erlauben Benutzern das Hochladen von Dateien, sind jedoch besorgt darüber, dass die von Benutzern hochgeladenen Dateien auf dem Server ausgeführt werden.
Beobachten Sie zunächst, wie die PHP-Datei ausgeführt wird. Geht man von einem Server mit einer PHP-Umgebung aus, dann verfügt dieser normalerweise über zwei Methoden, um PHP-Dateien extern auszuführen. Eine besteht darin, die Datei direkt über eine URL anzufordern, z. B. http://example.com/somefile.php. Die zweite Möglichkeit, die heutzutage häufig von PHP verwendet wird, besteht darin, alle Anfragen an index.php weiterzuleiten und irgendwie andere Dateien in diese Datei einzufügen. Daher gibt es zwei Möglichkeiten, Code aus einer PHP-Datei auszuführen: Führen Sie die Datei aus oder verwenden Sie die Methode include/include_once/require/require_once, um andere Dateien einzuführen, die ausgeführt werden müssen.
Tatsächlich gibt es eine dritte Methode: die Funktion eval(). Es kann den eingehenden String als PHP-Code ausführen. Diese Funktion wird in den meisten CMS-Systemen verwendet, um in der Datenbank gespeicherten Code auszuführen. Die Funktion eval() ist sehr gefährlich, aber wenn Sie sie verwenden, bedeutet dies normalerweise, dass Sie bestätigen, dass Sie etwas Gefährliches tun und keine anderen Optionen haben. Tatsächlich hat eval() seinen Nutzen und kann in manchen Situationen sehr nützlich sein. Aber wenn Sie ein Neuling sind, empfehle ich Ihnen nicht, es zu verwenden. Siehe diesen Artikel bei OWASP. Ich habe viel darüber geschrieben.
Es gibt also zwei Möglichkeiten, den Code in der Datei auszuführen: Führen Sie ihn direkt aus oder fügen Sie ihn in die auszuführende Datei ein. Wie kann man also verhindern, dass dies passiert?
Lösung?
Wie können wir wissen, dass eine Datei PHP-Code enthält? Schauen Sie sich den Erweiterungsnamen an, wenn er mit .php
endet, z. B. somefile.php
. Wir gehen davon aus, dass er PHP-Code enthält.
Wenn sich im Stammverzeichnis der Website eine somefile.php
-Datei befindet, dann greifen Sie im Browser auf http://example.com/somefile.php
zu, diese Datei wird ausgeführt und der Inhalt wird an den Browser ausgegeben.
Aber was passiert, wenn ich diese Datei umbenenne? Was passiert, wenn ich es in somefile.txt
oder somefile.jpg
umbenenne? Was bekomme ich? Ich hole mir den Inhalt. Es wird nicht ausgeführt. Es wird direkt von der Festplatte (oder dem Cache) gesendet.
Die Antwort auf reddit ist in diesem Punkt richtig. Das Umbenennen verhindert, dass eine Datei unerwartet ausgeführt wird. Warum halte ich diese Lösung für falsch?
Ich glaube, Ihnen ist das Fragezeichen aufgefallen, das ich nach „Lösung“ hinzugefügt habe. Dieses Fragezeichen macht Sinn. Heutzutage ist es fast unmöglich, in der URL der meisten Websites eine separate PHP-Datei zu sehen. Und selbst wenn dies der Fall ist, ist es absichtlich gefälscht, da .php
auf der URL erforderlich ist, um Abwärtskompatibilität mit älteren URL-Versionen zu erreichen.
Jetzt wird der Großteil des PHP-Codes im Handumdrehen eingeführt, da alle Anfragen an das Stammverzeichnis der Website gesendet werden index.php
. Diese Datei importiert andere PHP-Dateien gemäß bestimmten Regeln. Solche Regeln können (oder werden in Zukunft) in böswilliger Absicht verwendet werden. Wenn die Regeln Ihrer Anwendung die Einführung von Benutzerdateien zulassen, ist die Anwendung anfällig für Angriffe und Sie sollten sofort Maßnahmen ergreifen, um die Ausführung der Benutzerdateien zu verhindern.
Ist es in Ordnung, die Datei umzubenennen? ---Nein, das geht nicht!
Dem PHP-Parser ist die Dateierweiterung egal. Tatsächlich ist es allen Programmen egal. Doppelklicken Sie auf die Datei und die Datei wird vom entsprechenden Programm geöffnet. Die Dateierweiterung hilft dem Betriebssystem einfach dabei, zu erkennen, welches Programm zum Öffnen der Datei verwendet werden soll. Ein Programm kann jede Datei öffnen, solange das Programm die Datei lesen kann. Manchmal weigert sich ein Programm, eine Datei zu öffnen und zu bearbeiten. Das liegt aber nicht am Suffix, sondern am Dateiinhalt.
Der Server ist normalerweise so eingerichtet, dass er .php
Dateien ausführt und die Ausführungsergebnisse zurückgibt. Wenn Sie ein Bild anfordern .jpg
--- wird es unverändert von der Festplatte zurückgegeben. Was passiert, wenn Sie den Server auffordern, ein JPEG-Bild auf eine bestimmte Weise auszuführen? Wird der Server ausgeführt oder nicht?
Das Programm kümmert sich nicht um Dateinamen. Dabei spielt es keine Rolle, ob die Datei einen Namen hat oder ob es sich überhaupt um eine Datei handelt.
Es gibt mindestens zwei Situationen, in denen PHP Code ausführen kann:
Code zwischen <?php
und Code zwischen ?>
-Tags
Code zwischen =
- und ?>
-Tags
, obwohl die Datei mit gefüllt ist Wenn es sich um seltsame Binärdaten oder einen seltsamen geschützten Namen handelt, wird der Code im Tag trotzdem ausgeführt.
An dem Bild ist nichts auszusetzen
Es ist jetzt rein. Aber Sie wissen wahrscheinlich, dass das JPEG-Format das Hinzufügen einiger Kommentare zur Datei ermöglicht. Zum Beispiel das Kameramodell oder die Koordinatenadresse, die das Foto aufgenommen hat. Was wäre, wenn wir versuchen, dort PHP-Code einzufügen und zu include oder require ? Werfen wir einen Blick darauf!
Laden Sie dieses Bild auf Ihre Festplatte herunter. Oder Sie können selbst ein JPEG-Bild erstellen. Es spielt keine Rolle, welches Dateiformat Sie verwenden. Ich empfehle die Verwendung einer JPEG-Datei für die Demonstration, vor allem weil es sich um ein Bild handelt und sich der Text darin leicht bearbeiten lässt. Ich verwende einen Windows-Laptop und habe derzeit keinen Apple- oder Linux-Laptop (oder ein anderes UNIX-basiertes System) zur Hand. Deshalb werde ich gleich einen Screenshot dieses Betriebssystems veröffentlichen. Aber ich bin mir sicher, dass Sie das auch schaffen können.
Erstellen Sie eine Datei mit dem folgenden PHP-Code:
<h1>Problem?</h1> <img alt="So verhindern Sie, dass Benutzer ausführbare PHP-Dateien hochladen (mit Beispielen)" > <?php include "./troll-face.jpg";
Speichern Sie ein Bild und benennen Sie es troll-face.jpg
Das Bilder und PHP-Skriptdateien werden im selben Ordner abgelegt
Öffnen Sie den Browser, um diese PHP-Datei anzufordern
Wenn Sie Ihren PHP-Namen eingeben Datei index.php
und platzieren Sie sie im Dateistammverzeichnis oder einem beliebigen Dateiverzeichnis unter Ihrem Website-Verzeichnis.
Wenn Sie die oben genannten Schritte genau ausführen, sehen Sie diesen Bildschirm:
Bis jetzt ist alles in Ordnung. Es wird kein PHP-Code angezeigt und kein PHP-Code ausgeführt.
Jetzt fügen wir eine Frage hinzu:
Öffnen Sie das Dialogfeld mit den Dateieigenschaften oder führen Sie eine Anwendung aus, die das Bearbeiten von EXIF-Informationen ermöglicht
Wechseln Sie zur Registerkarte „Details“ oder bearbeiten Sie die Informationen anderweitig.
Scrollen Sie nach unten zu den Kameraparametern.
Kopieren Sie den Code unten nach Feld „Kamerahersteller“:
<?php echo "<h2>Yep, a problem!"; phpinfo(); ?>
Seite aktualisieren!
Offensichtlich ist etwas schief gelaufen!
Sie sehen das Bild auf der Seite. Das gleiche Bild existiert auch im PHP-Code der Seite. Der Code für das Bild wird ebenfalls ausgeführt.
Lange Rede, kurzer Sinn: Wenn wir diese unsicheren Dateien nicht in das Programm einbinden, werden die Skripte in den Dateien nicht ausgeführt.
Schauen Sie sich das Beispiel unten genau an.
Wenn jemand irgendwo sieht, dass ich falsch liege – korrigiert mich bitte, das ist ein ernstes Problem.
PHP ist eine Skriptsprache. Sie müssen immer auf eine Datei verweisen, die Pfade dynamisch kombiniert. Um Ihren Server zu schützen, müssen Sie daher die Pfade überprüfen und Verwechslungen zwischen Ihren Site-Dateien und den von Benutzern hochgeladenen oder erstellten Dateien verhindern. Wenn die Dateien des Benutzers von den Anwendungsdateien getrennt sind, können Sie den Pfad zur Datei überprüfen, bevor Sie „Hochladen“ oder „Erstellen“ verwenden. Wenn es sich in einem Ordner befindet, den Ihr Anwendungsskript zulässt, kann es diese Datei mit include_once oder require oder require_once einschließen. Wenn nicht, dann stellen Sie es nicht vor.
Wie überprüfe ich? Es ist ganz einfach. Sie müssen lediglich den $folder
(Datei-)Pfad mit einem Pfadordner vergleichen, der es dem Programm ermöglicht, Dateien zu importieren ( $file
).
// 不好的例子,不要用! if (substr($file, 0, strlen($folder)) === $folder) { include $file; }
Wenn der Speicherpfad von $folder
/path/to/folder
ist und der Speicherpfad von $file
/path/to/folder/and/file
ist, dann verwenden wir die Funktion substr() im Code, um ihre Pfade in negative Zeichenfolgen umzuwandeln Überprüfen Sie, ob sich die Dateien in verschiedenen Ordnern befinden – die Zeichenfolgen sind nicht gleich. Das Gegenteil ist der Fall.
上面的代码有两个重要的问题。如果 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() 它从路径结束时提取路径的一部分,直到它遇到第一个斜杠,但忽略字符串末尾的斜杠,参见示例。无论如何,你会收到一个安全的文件名。如果你觉得安全 - 那么是的这很安全。如果它被不法上传利用 - 你可以使用它来校验文件名是否安全。
另一个解决方案 --- 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 === '' OR $path === null OR $path === false OR $folder === '' 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 脚本。
不要信任用户。不要信任浏览器。构建似乎所有人都在提交病毒的后端。
当然,也不必害怕,这其实比看起来的简单。只要记住 “不要信任用户” 以及 “有功能解决此问题” 便可。
Das obige ist der detaillierte Inhalt vonSo verhindern Sie, dass Benutzer ausführbare PHP-Dateien hochladen (mit Beispielen). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!