이 문서의 내용은 사용자가 PHP 실행 파일을 업로드하는 것을 방지하는 방법에 대한 것입니다. 특정 참고 가치가 있으므로 도움이 될 수 있습니다.
모든 전문 PHP 개발자는 사용자가 업로드한 파일이 매우 위험하다는 것을 알고 있습니다. 백엔드 해커와 프런트엔드 해커 모두 이를 사용하여 문제를 일으킬 수 있습니다.
약 한 달 전 Reddit에서 PHP 업로드 취약점 탐지에 관한 기사를 읽고 기사를 쓰기로 결정했습니다. 사용자 darpernter가 까다로운 질문을 했습니다.
이름을 'helloworld.txt'로 바꾸더라도 공격자는 여전히 자신의 PHP 스크립트를 실행할 수 있습니까?가장 높은 답변은 다음과 같습니다.
파일 접미사가 .txt로 변경된 경우 , 그러면 php 파일로 실행되지 않으니 안심하셔도 되지만, .php.txt 접미사를 붙여 업로드되지 않았는지 다시 한 번 확인해주세요.죄송합니다. 위의 답변이 모두 틀린 것은 아니지만, 분명히 포괄적인 답변은 아닙니다. 놀랍게도 대부분의 답변이 매우 비슷했습니다.
이 문제를 명확하게 설명하고 싶습니다. 그래서 제가 이야기하려고 했던 내용이 좀 커져서 더 크게 만들기로 결정했습니다.
문제
사람들은 사용자에게 파일 업로드를 허용하지만, 사용자가 업로드한 파일이 서버에서 실행될까봐 걱정됩니다.
php 파일이 어떻게 실행되는지부터 살펴보세요. PHP 환경을 갖춘 서버를 가정하면 일반적으로 외부에서 PHP 파일을 실행하는 두 가지 방법이 있습니다. 하나는 http://example.com/somefile.php와 같은 URL을 사용하여 파일을 직접 요청하는 것입니다. 요즘 PHP에서 일반적으로 사용되는 두 번째 방법은 모든 요청을 index.php로 전달하고 어떻게든 이 파일에 다른 파일을 추가하는 것입니다. 따라서 PHP 파일에서 코드를 실행하는 방법에는 두 가지가 있습니다. 파일을 실행하거나 include/include_once/require/require_once 메소드를 사용하여 실행해야 하는 다른 파일을 도입하는 것입니다.
사실 세 번째 방법인 eval() 함수가 있습니다. 들어오는 문자열을 PHP 코드로 실행할 수 있습니다. 이 함수는 대부분의 CMS 시스템에서 데이터베이스에 저장된 코드를 실행하는 데 사용됩니다. eval() 함수는 매우 위험하지만, 이 함수를 사용하면 일반적으로 위험한 일을 하고 있고 다른 옵션이 없음을 확인한다는 의미입니다. 실제로 eval()에는 용도가 있으며 일부 상황에서는 매우 유용할 수 있습니다. 하지만 초보자라면 사용을 권장하지 않습니다. OWASP에서 이 기사를 참조하세요. 나는 그것에 대해 많은 것을 썼다.
그래서 파일에 있는 코드를 실행하는 방법에는 두 가지가 있습니다. 직접 실행하거나 실행 중인 파일에 도입하는 것입니다. 그렇다면 이런 일이 발생하지 않도록 하려면 어떻게 해야 할까요?
해결책?
파일에 PHP 코드가 포함되어 있는지 어떻게 알 수 있나요? 확장자 이름을 확인하세요. somefile.php
처럼 .php
로 끝나면 PHP 코드가 포함된 것으로 생각됩니다. .php
结尾的,像 somefile.php
我们就认为它里面有 php 代码。
如果在网站根目录下有一个 somefile.php
文件,那么在浏览器访问 http://example.com/somefile.php
,这个文件就会被执行并且输出内容到浏览器上。
但是如果我重命名这个文件会怎样?如果我把它重命名为 somefile.txt
或者是 somefile.jpg
呢?我会得到什么?我会得到它的内容。它不会被执行。它会从硬盘(或者缓存)直接被发送过来。
在这点上 reddit 社区上的答案是对的。重命名能防止一个文件被非预期的执行,那么为什么我认为这种解决方法是错的呢?
我相信你注意到我在 “解决方法” 后面加的问号。这个问号是有意义的。现在大多数网站的 URL 上几乎看不到单独的 php 文件。并且就算有,也是人为故意伪造的,因为 URL 上需要有 .php
来实现对老版本 URL 的向后兼容。
现在绝大部分 php 代码是在运行中被引入的,因为所有请求都被发送到了网站根目录的 index.php
。这个文件会根据特定的规则引入其他 php 文件。这种规则可能(或者在将来会)被恶意使用。如果你应用的规则允许引入用户的文件,那么应用会容易遭到攻击,你应该立即采取措施防止用户的文件被执行。
重命名文件名可以吗? --- 不,办不到!
PHP解析器不关心文件的后缀名。事实上,所有程序都不关心。双击文件,文件会被对应的程序打开。文件后缀名只是帮助操作系统识别用什么程序打开文件。只要程序有读取文件的能力,程序就可以打开任何文件。有时程序拒绝打开和操作文件。但那并不是因为后缀名,是文件内容所致。
服务器通常被设置成执行 .php
文件并将执行结果回复输出。如果你请求图片 .jpg
somefile.php
파일이 있다면 브라우저에서 http://example.com/somefile.php
에 접속하세요. 파일이 실행되고 콘텐츠가 브라우저에 출력됩니다. 🎜🎜하지만 이 파일의 이름을 바꾸면 어떻게 되나요? 이름을 somefile.txt
또는 somefile.jpg
로 바꾸면 어떻게 되나요? 나는 무엇을 얻을 것인가? 내용을 가져오겠습니다. 실행되지 않습니다. 하드 디스크(또는 캐시)에서 직접 전송됩니다. 🎜🎜reddit의 답변이 바로 이 점에 있습니다. 이름을 바꾸면 파일이 예기치 않게 실행되는 것을 방지할 수 있는데, 이 솔루션이 잘못되었다고 생각하는 이유는 무엇입니까? 🎜🎜제가 "해결책" 뒤에 추가한 물음표를 보셨을 거라 생각합니다. 이 물음표는 의미가 있습니다. 요즘에는 대부분의 웹사이트 URL에서 별도의 PHP 파일을 보는 것이 거의 불가능합니다. 그리고 있다고 하더라도 이전 버전의 URL과의 호환성을 달성하려면 URL에 .php
가 필요하기 때문에 의도적으로 위조된 것입니다. 🎜🎜이제 대부분의 PHP 코드는 런타임에 도입됩니다. 모든 요청이 웹사이트 루트 디렉터리의 index.php
로 전송되기 때문입니다. 이 파일은 특정 규칙에 따라 다른 PHP 파일을 가져옵니다. 이러한 규칙은 악의적으로 사용될 수 있습니다. 애플리케이션의 규칙이 사용자 파일의 도입을 허용하는 경우 애플리케이션은 공격에 취약하므로 사용자 파일이 실행되지 않도록 즉시 조치를 취해야 합니다. 🎜.php
파일을 실행하고 실행 결과를 다시 출력하도록 설정되어 있습니다. 이미지 .jpg
를 요청하면 --- 디스크에서 변경되지 않은 상태로 반환됩니다. 서버에 jpeg 이미지를 특정 방식으로 실행하도록 요청하면 어떻게 되나요? 서버가 실행될 것인가, 실행되지 않을 것인가? 🎜프로그램은 파일 이름에 관심이 없습니다. 파일에 이름이 있는지 또는 파일인지 여부는 중요하지 않습니다.
PHP가 코드를 실행할 수 있는 상황은 최소한 두 가지가 있습니다:
<?php
와 ?> 사이의 코드 ; 코드 사이> 태그
<?php
和 ?>
标记之间
代码介于 =
和 ?>
标记之间
即使文件中填充了一些奇怪的二进制数据或一些奇怪的保护名称,该标记中的代码仍然会被执行。
该图片没有问题
它现在很纯净。但是您可能知道 JPEG 格式允许在文件中添加一些注释。比如,拍摄照片的相机型号或坐标地址。如果我们试图在里面放一些PHP代码并尝试 include 或 require 呢?让我们来看看吧!
下载这个图片到你的硬盘上。或者你自己去弄一张 JPEG 图片也行。你随便用什么格式的文件都无所谓。我建议用一个 JPEG 文件来演示,主要是因为它是一张图片且易于在其中进行文本编辑。我用的是一个 Windows的笔记本,目前我手头上没有 Apple 或 Linux(或其他UNIX系的系统)的笔记本。所以一会我会发一个这个 OS 下的屏幕快照。但是我确信你肯定也能做这个事。
用以下这段 PHP 代码建个文件:
<h1>Problem?</h1> <img src="/static/imghwm/default1.png" data-src="troll-face.jpg" class="lazy" alt="사용자가 PHP 실행 파일을 업로드하지 못하도록 방지하는 방법(예제 포함)" > <?php include "./troll-face.jpg";
保存一个图片命名为troll-face.jpg
把图片和 php 脚本文件都放在同一个文件夹下
打开浏览器请求这个 php 文件
如果你把你的 php 文件命名为 index.php
,然后把它放在文件根目录或者放在你网站目录下的任何一个文件目录中。
如果你准确完成了上述步骤,你就可以看到这个画面:
到此这都没毛病。没 PHP 代码展示,也没有 PHP 代码被执行。
现在,我们来添加一个问题:
打开文件属性对话框或运行一些允许编辑 EXIF 信息的应用程序
切换到 Details 选项卡或以其他方式编辑该信息
向下滚动到 camera 参数
将下面代码复制到 “camera maker” 字段后面:
<?php echo "<h2>Yep, a problem!"; phpinfo(); ?>
刷新页面!
很明显出现了一点问题!
您在页面上看到了该图片。相同的图片还存在页面的 PHP 代码中。图片的代码也被执行了。
长话短说: 如果我们不在程序种引入这些不安全的文件,文件中的脚本就不会执行。
仔细看下面的例子。
如果有人在某处看到我错了 - 请纠正我,这是一个严重的问题。
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
=
와 ?>
태그 사이의 코드🎜🎜 짝수 파일이 이상한 바이너리 데이터나 이상한 보호된 이름으로 채워져 있으면 해당 태그의 코드는 계속 실행됩니다. 🎜🎜/path/to/folder/../../../../../../../another/path/from/root/🎜🎜🎜이미지 이름을
troll-face.jpg
🎜🎜🎜저장합니다. 이미지와 PHP 스크립트 파일을 같은 곳에 넣습니다. 폴더🎜🎜🎜브라우저를 열어 이 PHP 파일을 요청하세요🎜🎜PHP 파일 이름을 index.php
로 지정하고 파일 루트에 넣으세요. 디렉토리 또는 웹사이트 디렉토리 아래의 파일 디렉토리. 🎜🎜위 단계를 정확하게 완료하시면 다음 화면을 보실 수 있습니다: 🎜🎜include $folder . "/" . $_GET['some']; // or $_POST, or whatever🎜🎜🎜페이지를 새로고침하세요! 🎜🎜
$folder
(파일) 경로를 프로그램이 파일( $file
)을 가져올 수 있는 경로 폴더와 비교하면 됩니다. 🎜move_uploaded_file($filename, $folder . '/' . $filename);🎜
$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 중국어 웹사이트의 기타 관련 기사를 참조하세요!