ホームページ >バックエンド開発 >PHPチュートリアル >ユーザーが PHP 実行可能ファイルをアップロードできないようにする方法 (例付き)
この記事の内容は、ユーザーが PHP 実行ファイルをアップロードできないようにする方法に関するもので、一定の参考価値があります。困っている友人は参考にしてください。お役に立てれば幸いです。
プロの PHP 開発者は誰でも、ユーザーがアップロードしたファイルが非常に危険であることを知っています。バックエンドハッカーとフロントエンドハッカーの両方がこれらを使用して問題を引き起こす可能性があります。
1か月ほど前、redditでPHPアップロードの脆弱性検出に関する記事を読んだので、記事を書くことにしました。ユーザー darpernter は難しい質問をしました:
名前を「helloworld.txt」に変更したとしても、攻撃者は引き続き php スクリプトを実行できますか?一番の答えは次のとおりです:
ファイルのサフィックスが変更された場合.txt にアップロードすると、php ファイルとして実行されることはないので安心してください。ただし、.php.txt 接尾辞を付けてアップロードされていないことをもう一度確認してください。申し訳ありませんが、質問に対する正しい答えは異なります。上記の答えはすべて間違っているわけではありませんが、明らかに包括的ではありません。驚いたことに、ほとんどの回答は非常に似ていました。
この問題についてわかりやすく説明したいと思います。それで、私が話そうとした内容が少し大きくなったので、さらに大きくすることにしました。
問題
ユーザーはファイルのアップロードを許可していますが、ユーザーがアップロードしたファイルがサーバー上で実行されるのではないかと心配しています。
まず、php ファイルがどのように実行されるかを見てみましょう。サーバーに php 環境があるとすると、通常、php ファイルを外部から実行する方法が 2 つあります。 1 つは、http://example.com/somefile.php のような URL を使用してファイルを直接リクエストすることです。 2 番目の方法は、現在 PHP で一般的に使用されており、すべてのリクエストをindex.php に転送し、何らかの方法でこのファイルに他のファイルを導入します。したがって、php ファイルからコードを実行するには 2 つの方法があります。ファイルを実行するか、include/include_once/require/require_once メソッドを使用して、実行する必要がある他のファイルを導入します。
実際には、3 番目のメソッド eval() 関数があります。受信した文字列をphpコードとして実行できます。この関数は、ほとんどの CMS システムでデータベースに保存されているコードを実行するために使用されます。 eval() 関数は非常に危険ですが、これを使用すると、通常、何か危険なことをしていることを確認し、他に選択肢がないことを意味します。実際、 eval() には用途があり、状況によっては非常に役立ちます。しかし、初心者の場合は、それを使用することはお勧めしません。 OWASP のこの記事を参照してください。それにたくさん書きました。
したがって、ファイル内のコードを実行するには、コードを直接実行するか、実行中のファイルにコードを導入するかの 2 つの方法があります。では、このような事態を回避するにはどうすればよいでしょうか?
解決策?
ファイルに php コードが含まれていることをどのように確認すればよいでしょうか?拡張子名を見て、somefile.php
のように .php
で終わっている場合は、PHP コードが含まれていると考えられます。
Web サイトのルート ディレクトリに somefile.php
ファイルがある場合は、ブラウザで http://example.com/somefile.php
にアクセスします。 , このファイルが実行され、内容がブラウザに出力されます。
しかし、このファイルの名前を変更するとどうなるでしょうか?名前を somefile.txt
または somefile.jpg
に変更したらどうなりますか?何をもらえるでしょうか?その内容を入手します。実行されません。ハードディスク (またはキャッシュ) から直接送信されます。
この点については、reddit コミュニティの回答が正しいです。名前を変更すると、ファイルが予期せず実行されるのを防ぐことができるのに、なぜこの解決策が間違っていると思うのでしょうか?
「解決策」の後に私が追加した疑問符に気づいたと思います。この疑問符には意味があります。現在、ほとんどの Web サイトの URL 上に個別の php ファイルを表示することはほとんど不可能です。また、存在するとしても、古いバージョンの URL との下位互換性を実現するには URL に .php
が必要であるため、意図的に偽造されています。
すべてのリクエストが Web サイトのルート ディレクトリにある index.php
に送信されるため、ほとんどの PHP コードが実行時に導入されます。このファイルは、特定のルールに従って他の PHP ファイルをインポートします。このようなルールは悪用される可能性があります (または将来悪用される可能性があります)。アプリケーションのルールでユーザー ファイルの導入が許可されている場合、アプリケーションは攻撃に対して脆弱になるため、ユーザーのファイルが実行されないように直ちに措置を講じる必要があります。
ファイル名を変更しても大丈夫ですか? --- いや、無理です!
PHP パーサーはファイル拡張子を考慮しません。実際、すべてのプログラムは気にしません。ファイルをダブルクリックすると、対応するプログラムによってファイルが開きます。ファイル拡張子は、オペレーティング システムがファイルを開くために使用するプログラムを識別するのに役立つだけです。プログラムは、ファイルを読み取る機能がある限り、任意のファイルを開くことができます。場合によっては、プログラムがファイルを開いて操作することを拒否することがあります。しかし、それはサフィックスが原因ではなく、ファイルの内容が原因です。
サーバーは通常、.php
ファイルを実行し、実行結果を出力するように設定されています。画像 .jpg
--- 画像をリクエストすると、ディスクから変更されずに返されます。サーバーに特定の方法で JPEG 画像を実行するように要求するとどうなるでしょうか?サーバーは実行されるかどうか?
プログラムはファイル名を気にしません。ファイルに名前があるかどうか、あるいはそれがファイルであるかどうかさえ問題ではありません。
PHP がコードを実行できる状況は少なくとも 2 つあります:
コードは次のとおりです。 <?php
と ?>
タグの間
=
と の間のコード?> ;
タグ間
ファイルに奇妙なバイナリ データや奇妙な保護名が埋め込まれている場合でも、このタグ内のコードは実行されます。
#画像に問題はありません。現在は純正です。ただし、JPEG 形式ではファイルにコメントを追加できることはおそらくご存知でしょう。たとえば、写真を撮影したカメラのモデルや座標アドレスなどです。そこに PHP コードを入れて include または require しようとするとどうなるでしょうか?見てみましょう! 質問! 1このイメージをハード ドライブにダウンロードします。または、JPEG 画像を自分で入手することもできます。使用するファイル形式は関係ありません。デモンストレーションには JPEG ファイルを使用することをお勧めします。主に、JPEG ファイルは画像であり、テキスト編集が簡単であるためです。私は Windows ラップトップを使用していますが、現在 Apple または Linux (またはその他の UNIX ベース システム) ラップトップを手元に持っていません。そこで、この OS のスクリーンショットをすぐに投稿します。しかし、あなたにもきっとできると思います。 次の PHP コードを使用してファイルを作成します:
<h1>Problem?</h1> <img alt="ユーザーが PHP 実行可能ファイルをアップロードできないようにする方法 (例付き)" > <?php include "./troll-face.jpg";
troll-face.jpg
という名前を付け、ファイルのルート ディレクトリまたは Web サイトのディレクトリ下の任意のファイル ディレクトリに置きます。 上記の手順を正確に完了すると、次の画面が表示されます。
今のところ何も問題はありません。 PHP コードは表示されず、PHP コードも実行されません。
さあ、質問を追加しましょう:
<?php echo "<h2>Yep, a problem!"; phpinfo(); ?>
ページを更新してください。
#明らかに何か問題が発生しました。
ページにこの画像が表示されます。同じ画像がページの PHP コードにも存在します。画像のコードも実行されます。 どうすればいいでしょうか?!! 1簡単に言うと、これらの安全でないファイルをプログラムに含めないと、ファイル内のスクリプトは実行されません。 以下の例を注意深く見てください。 最終的な答えは?PHP はスクリプト言語です。パスを動的に組み合わせる何らかのファイルを常に参照する必要があります。したがって、サーバーを保護するには、パスを確認し、サイト ファイルとユーザーがアップロードまたは作成したファイルとの間の混同を防ぐ必要があります。ユーザーのファイルがアプリケーション ファイルとは別の場合は、アップロードまたは作成を使用する前にファイルへのパスを確認できます。アプリケーション スクリプトが許可するフォルダー内にある場合は、include_once または require または require_once を使用してこのファイルを含めることができます。そうでない場合は、導入しないでください。
どうやって確認しますか?これはとても簡単です。必要なのは、$folder
(ファイル) パスを、プログラムがファイルをインポートできるパス フォルダー ($file ) と比較することだけです。
// 不好的例子,不要用! if (substr($file, 0, strlen($folder)) === $folder) { include $file; }
$folder のストレージ パスが /path/to/folder で、
$file のストレージ パスが
の場合/path /to/folder/and/file の場合、コード内で substr() 関数を使用して、パスを判定のために負の文字列に変換します。ファイルが異なるフォルダーにある場合、この文字列は使用されません。等しい。その逆も真実です。
上面的代码有两个重要的问题。如果 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 脚本。
不要信任用户。不要信任浏览器。构建似乎所有人都在提交病毒的后端。
当然,也不必害怕,这其实比看起来的简单。只要记住 “不要信任用户” 以及 “有功能解决此问题” 便可。
以上がユーザーが PHP 実行可能ファイルをアップロードできないようにする方法 (例付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。