防止直接访问 php 包含文件

Posted

技术标签:

【中文标题】防止直接访问 php 包含文件【英文标题】:Prevent direct access to a php include file 【发布时间】:2010-09-29 09:33:57 【问题描述】:

我有一个 php 文件,我将专门用作包含。因此,当通过键入 URL 而不是被包含来直接访问它时,我想抛出一个错误而不是执行它。

基本上我需要在php文件中做如下检查:

if ( $REQUEST_URL == $URL_OF_CURRENT_PAGE ) die ("Direct access not premitted");

有没有简单的方法可以做到这一点?

【问题讨论】:

而不是 die() 你应该测试 'header("HTTP/1.1 404 File Not Found", 404);出口;'。这将(至少在 apache 上)使服务器返回正常的 404 页面。 这里有两种简单的方法,我已经解释了在 PHP 包含的文件中禁用直接访问 - codespeedy.com/disable-direct-access-to-the-php-include-file 【参考方案1】:

将此添加到您只想包含的页面中

<?php
if(!defined('MyConst')) 
   die('Direct access not permitted');

?>

然后在包含它的页面上添加

<?php
define('MyConst', TRUE);
?>

【讨论】:

我真的需要学会更快地打字。这与我建议的方式相同,因为它比使用变量检查的方法更安全。由于某些 PHP 设置可能会覆盖该变量。 这就是一些“主流”应用程序处理它的方式。我知道 Joomla 就是这样做的,我认为 Wiki、Wordpress 和其他网站也是如此。 只需发送 404 标头并退出 - 错误页面看起来与正常的 404 页面相同(至少在 Apache 上)。 @Smile.Hunter:这是关于阻止访问直接查看您的包含/库脚本文件,答案有效。如果他们在您的服务器上创建了somefile.php 并在其中添加了您的定义,那仍然不允许他们直接访问包含文件。它会让他们“包含”您的库文件,但如果他们足够远可以在您的服务器上创建文件并知道您的定义/包含脚本,那么您可能会遇到其他问题,这些问题可能会首先否定使用您的定义编写自己的文件. 以相同结果拒绝访问的更短的方法是:defined('MyConst') || die('Direct access not permitted');【参考方案2】:

对于通用的“在您可能完全控制或可能不完全控制的 Apache 服务器上运行的 PHP 应用程序”情况,最简单的方法是将您的包含放在一个目录中,并在您的 .htaccess 文件中拒绝对该目录的访问。为了省去人们在谷歌上搜索的麻烦,如果您使用的是 Apache,请将其放在您不希望访问的目录中名为“.htaccess”的文件中:

Deny from all

如果您实际上可以完全控制服务器(这些天甚至对于小型应用程序比我第一次写这个答案时更常见),最好的方法是将要保护的文件粘贴到您的 Web 服务器的目录之外是从服务。因此,如果您的应用位于 /srv/YourApp/ 中,请将服务器设置为提供来自 /srv/YourApp/app/ 的文件并将包含内容放入 /srv/YourApp/includes 中,因此实际上没有任何 URL 可以访问它们。

【讨论】:

如果您对服务器有完全的控制权,最好将配置放入虚拟主机配置文件的目录指令中。 Apache 在启动时只读取一次,每次访问都会读取 .htaccess 并降低服务器速度 最好有一个示例 .htaccess 文件作为这个答案的一部分。 &lt;Files ~ "\.inc$"&gt; Order Allow,Deny Deny from All &lt;/Files&gt; @James:另外,并不是每个人都认为 Stack Overflow 应该是一个“请发送代码”网站。如果它清楚地回答了这个问题,那么它就是一个很好的答案。提供一个不需要的示例只会鼓励复制和粘贴编码。 我试过这个,如果你这样做 Deny from all 它甚至不能在包含中工作,它会 404。【参考方案3】:

我有一个文件,当它被包含和直接访问时,我需要采取不同的行动(主要是 print()return())这是一些修改后的代码:

if(count(get_included_files()) ==1) exit("Direct access not permitted.");

被访问的文件始终是包含文件,因此 == 1。

【讨论】:

这实际上是个绝妙的主意,检查包含的文件数。我想知道哪个更好:使用定义还是使用这种方法?这似乎更加独立。 我第一次看到有人想出这个。不过我不知道为什么,因为它似乎是自包含的,并且它直接测量您实际想知道的内容(是否包含)而不是测量假定相关的事物(例如某个常数或.htaccess 禁止的某些位置)。漂亮。 这个真的很酷,因为使用 .htaccess 来阻止所有 .php 文件可能并非一直可行,因为同一目录中可能有一些文件需要直接调用,甚至需要通过 javascripts 调用.感谢这个好主意! 这仅适用于 PHP5 及更高版本。 Pre-PHP5 你想再次比较 0 而不是 1。 这是一个非常聪明的解决方案,允许您控制直接访问,而不仅仅是阻止它——这就是我喜欢的。我通常在文件本身中包含单元测试,这样我可以将单元测试包装在这个 if 语句中。想知道它的效率如何..【参考方案4】:

防止直接访问文件的最佳方法是将它们放在网络服务器文档根目录之外(通常是上一层)。您仍然可以包含它们,但不可能有人通过 http 请求访问它们。

我通常一路走下去,并将我的所有 PHP 文件放在文档根目录之外,除了 bootstrap file - 文档根目录中的一个单独的 index.php,它开始路由整个网站/应用程序。

【讨论】:

如果您能够这样做,这是一个很好的解决方案。我最近才开始使用共享的虚拟主机,并发现许多烦恼之一是所有内容都必须在 docroot 中。 在与我合作的每个托管服务提供商中,我总是可以访问(确切地说)文档根目录之上的一层。 在某些主机(包括我当前的主机)上,您可以将您的域指向您希望的任何文件夹。 这也是一个不错的选择 .. 使用 preg_match -> if ( preg_match("~globalfile\.php~i", $_SERVER['PHP_SELF'] ) ) die('

设备安全警报 - 不允许直接访问!您的 IP 已被记录!

'); // 停止进一步执行

那个 url devzone.zend.com/node/view/id/70 现在是 404。答案应该包括最初从那个不存在的 url 使用的代码。【参考方案5】:

1:检查包含文件的数量

if( count(get_included_files()) == ((version_compare(PHP_VERSION, '5.0.0', '>='))?1:0) )

    exit('Restricted Access');

逻辑:如果未满足最小包含计数,PHP 将退出。请注意,在 PHP5 之前,基本页面不被视为包含。


2:定义和验证全局常量

// In the base page (directly accessed):
define('_DEFVAR', 1);

// In the include files (where direct access isn't permitted):
defined('_DEFVAR') or exit('Restricted Access');

逻辑:如果未定义常量,则执行不会从基本页面开始,PHP 将停止执行。

注意,为了跨升级和未来更改的可移植性,使这种身份验证方法模块化将显着减少编码开销,因为更改不需要硬编码到每个文件。

// Put the code in a separate file instead, say 'checkdefined.php':
defined('_DEFVAR') or exit('Restricted Access');

// Replace the same code in the include files with:
require_once('checkdefined.php');

这样可以将额外的代码添加到checkdefined.php用于记录和分析目的,以及生成适当的响应。

功劳归于功劳:可移植性的绝妙想法来自this answer。


3:远程地址授权

// Call the include from the base page(directly accessed):
$includeData = file_get_contents("http://127.0.0.1/component.php?auth=token");

// In the include files (where direct access isn't permitted):
$src = $_SERVER['REMOTE_ADDR']; // Get the source address
$auth = authoriseIP($src); // Authorisation algorithm
if( !$auth ) exit('Restricted Access');

这种方法的缺点是执行隔离,除非内部请求提供了会话令牌。在单服务器配置的情况下通过环回地址进行验证,或者在多服务器或负载平衡服务器基础架构的情况下通过地址白名单进行验证。


4:令牌授权

与上一种方法类似,可以使用 GET 或 POST 将授权令牌传递给包含文件:

if($key!="serv97602")header("Location: ".$dart);exit();

一种非常混乱的方法,但如果以正确的方式使用,可能同时也是最安全和最通用的方法。


5:网络服务器特定配置

大多数服务器允许您为单个文件或目录分配权限。您可以将所有包含在此类受限目录中,并将服务器配置为拒绝它们。

例如在 APACHE 中,配置存储在 .htaccess 文件中。教程here.

注意但是,我不推荐特定于服务器的配置,因为它们不利于跨不同 Web 服务器的可移植性。在拒绝算法复杂或拒绝目录列表相当大的内容管理系统等情况下,它可能只会使重新配置会话变得相当可怕。最后最好在代码中处理。


6:将包含放置在站点根目录之外的安全目录中

由于服务器环境中的访问限制,最不受欢迎,但如果您可以访问文件系统,这是一种相当强大的方法。

//Your secure dir path based on server file-system
$secure_dir=dirname($_SERVER['DOCUMENT_ROOT']).DIRECTORY_SEPARATOR."secure".DIRECTORY_SEPARATOR;
include($secure_dir."securepage.php");

逻辑:

用户无法请求htdocs 文件夹之外的任何文件,因为这些链接超出了网站地址系统的范围。 php 服务器本地访问文件系统,因此可以访问计算机上的文件,就像具有所需权限的普通程序一样。 通过将包含文件放在此目录中,您可以确保 php 服务器可以访问它们,同时拒绝用户进行热链接。 即使网络服务器的文件系统访问配置未正确完成,此方法也可以防止这些文件意外公开。

请原谅我的非正统编码约定。任何反馈都表示赞赏。

【讨论】:

我喜欢2号【参考方案6】:

Chuck 解决方案的替代(或补充)方法是通过在 .htaccess 文件中添加类似内容来拒绝访问与特定模式匹配的文件

<FilesMatch "\.(inc)$">
    Order deny,allow
    Deny from all
</FilesMatch>

【讨论】:

我相信使用 .inc.php 会更好,这是一种常见的做法【参考方案7】:

实际上我的建议是做所有这些最佳实践。

将文档放在 webroot 之外或被 web 服务器拒绝访问的目录中 和 在可见文档中使用隐藏文档检查的定义:
      if (!defined(INCL_FILE_FOO)) 
          header('HTTP/1.0 403 Forbidden');
          exit;
      

这样,如果文件以某种方式放错了位置(错误的 ftp 操作),它们仍然受到保护。

【讨论】:

【参考方案8】:

我曾经遇到过这个问题,解决了:

if (strpos($_SERVER['REQUEST_URI'], basename(__FILE__)) !== false) ...

但理想的解决方案是将文件放在网络服务器文档根目录之外,如另一个 anwser 中所述。

【讨论】:

【参考方案9】:

我想直接限制对 PHP 文件的访问,但也可以通过jQuery $.ajax (XMLHttpRequest) 调用它。这对我有用。

if (empty($_SERVER["HTTP_X_REQUESTED_WITH"]) && $_SERVER["HTTP_X_REQUESTED_WITH"] != "XMLHttpRequest") 
    if (realpath($_SERVER["SCRIPT_FILENAME"]) == __FILE__)  // direct access denied
        header("Location: /403");
        exit;
    

【讨论】:

【参考方案10】:

你最好用一个入口来构建应用程序,即所有文件都应该从 index.php 访问

把它放在 index.php 中

define(A,true);

此检查应在每个链接文件中运行(通过 require 或 include)

defined('A') or die(header('HTTP/1.0 403 Forbidden'));

【讨论】:

【参考方案11】:

最简单的方法是在调用include的文件中设置一些变量,比如

$including = true;

然后在包含的文件中,检查变量

if (!$including) exit("direct access not permitted");

【讨论】:

如果 register_globals 开启,这很危险。 如果 register_globals 开启,PHP 是危险的。【参考方案12】:
debug_backtrace() || die ("Direct access not permitted");

【讨论】:

【参考方案13】:

我的答案在方法上有所不同,但包括此处提供的许多答案。我会推荐一种多管齐下的方法:

    .htaccess 和 Apache 限制肯定 defined('_SOMECONSTANT') or die('Hackers! Be gone!');

但是defined or die 方法有许多失败之处。首先,测试和调试的假设是一个真正的痛苦。其次,如果你改变主意,它会涉及到可怕的、令人麻木的、无聊的重构。 “查找和替换!”你说。是的,但是你有多确定它在任何地方都写得完全一样,嗯?现在将它与数千个文件相乘...o.O

然后是 .htaccess。如果您的代码被分发到管理员不那么谨慎的网站上会发生什么?如果您仅依靠 .htaccess 来保护您的文件,您还需要 a) 备份,b) 一盒用来擦干眼泪的纸巾,c) 一个灭火器来扑灭来自人们的所有仇恨邮件中的火焰使用您的代码。

所以我知道这个问题要求的是“最简单”,但我认为这要求的是更“防御性编码”。

我的建议是:

    在您的任何脚本之前require('ifyoulieyougonnadie.php');不是 include() 并作为defined or die 的替代品)

    ifyoulieyougonnadie.php 中,做一些逻辑工作——检查不同的常量、调用脚本、本地主机测试等——然后实现你的die(), throw new Exception, 403 等。

    我正在使用两个可能的入口点创建自己的框架——主要的 index.php(Joomla 框架)和 ajaxrouter.php(我的框架)——所以根据入口点,我会检查不同的东西。如果对ifyoulieyougonnadie.php 的请求不是来自这两个文件之一,我知道正在发生恶作剧!

    但是如果我添加一个新的入口点呢?不用担心。我只是更改ifyoulieyougonnadie.php 并且我已排序,加上没有“查找和替换”。万岁!

    如果我决定移动我的一些脚本来创建一个不同的框架,它没有相同的常量defined(),该怎么办? ...万岁! ^_^

我发现这种策略让开发变得更有趣,但更少:

/**
 * Hmmm... why is my netbeans debugger only showing a blank white page 
 * for this script (that is being tested outside the framework)?
 * Later... I just don't understand why my code is not working...
 * Much later... There are no error messages or anything! 
 * Why is it not working!?!
 * I HATE PHP!!!
 * 
 * Scroll back to the top of my 100s of lines of code...
 * U_U
 *
 * Sorry PHP. I didn't mean what I said. I was just upset.
 */

 // defined('_JEXEC') or die();

 class perfectlyWorkingCode 

 perfectlyWorkingCode::nowDoingStuffBecauseIRememberedToCommentOutTheDie();

【讨论】:

【参考方案14】:

除了 .htaccess 方式之外,我还在各种框架中看到了一种有用的模式,例如在 ruby​​ on rails 中。它们在应用程序根目录中有一个单独的 pub/ 目录,而库目录与 pub/ 位于 同一级别 的目录中。像这样的东西(不理想,但你明白了):

app/
 |
 +--pub/
 |
 +--lib/
 |
 +--conf/
 |
 +--models/
 |
 +--views/
 |
 +--controllers/

您将 Web 服务器设置为使用 pub/ 作为文档根目录。这为您的脚本提供了更好的保护:虽然它们可以从文档根目录中访问以加载必要的组件,但无法从 Internet 访问这些组件。除了安全之外的另一个好处是一切都在一个地方。

此设置比仅在每个包含的文件中创建检查要好,因为“不允许访问”消息是攻击者的线索,并且比 .htaccess 配置更好,因为它不是基于白名单的:如果你搞砸了文件扩展名在 lib/、conf/ 等目录中不可见。

【讨论】:

半天我只想评论一下,你上面描述的模型叫做MVC(模型-视图-控制器)模型。如果您愿意,请检查谷歌并在您的答案中添加更多信息。此外,MVC 不仅支持 Ruby on Rails 和 ASP.NET 应用程序,还支持 PHP(参见 Lavarel、CakePHP)。【参考方案15】:

什么 Joomla!确实是在根文件中定义一个常量并检查包含的文件中是否定义了相同的常量。

defined('_JEXEC') or die('Restricted access');

否则

正如大多数框架(如 CodeIgniter)所推荐的那样,通过将所有文件放在 webroot 目录之外,可以将所有文件保持在 http 请求的范围之外。

甚至通过在包含文件夹中放置一个 .htaccess 文件并编写规则,您可以防止直接访问。

【讨论】:

【参考方案16】:

如果更准确地说,你应该使用这个条件:

if (array_search(__FILE__, get_included_files()) === 0) 
    echo 'direct access';

else 
    echo 'included';

get_included_files() 返回包含所有包含文件名称的索引数组(如果文件被执行,则它被包含并且其名称在数组中)。 因此,当直接访问文件时,它的名称是数组中的第一个,数组中的所有其他文件都被包括在内。

【讨论】:

【参考方案17】:

执行以下操作:

<?php
if ($_SERVER['SCRIPT_FILENAME'] == '<path to php include file>') 
    header('HTTP/1.0 403 Forbidden');
    exit('Forbidden');

?>

【讨论】:

这不会阻止它在浏览器中加载。【参考方案18】:
<?php
if (eregi("YOUR_INCLUDED_PHP_FILE_NAME", $_SERVER['PHP_SELF']))  
 die("<h4>You don't have right permission to access this file directly.</h4>");

?>

将上面的代码放在包含的 php 文件的顶部。

例如:

<?php
if (eregi("some_functions.php", $_SERVER['PHP_SELF'])) 
    die("<h4>You don't have right permission to access this file directly.</h4>");


    // do something
?>

【讨论】:

if ( preg_match("~globalfile\.php~i", $_SERVER['PHP_SELF'] ) ) die('

设备安全警报 - 直接不允许访问!您的 IP 已被记录!

'); // 停止进一步执行 whre ~ 是分隔符

【参考方案19】:

以下代码用于 Flatnux CMS (http://flatnux.altervista.org):

if ( strpos(strtolower($_SERVER['SCRIPT_NAME']),strtolower(basename(__FILE__))) )

    header("Location: ../../index.php");
    die("...");

【讨论】:

【参考方案20】:

我发现这个只适用于 php 且不变的解决方案适用于 http 和 cli:

定义一个函数:

function forbidDirectAccess($file) 
    $self = getcwd()."/".trim($_SERVER["PHP_SELF"], "/");
    (substr_compare($file, $self, -strlen($self)) != 0) or die('Restricted access');

在你想阻止直接访问的文件中调用函数:

forbidDirectAccess(__FILE__);

上面给出的这个问题的大多数解决方案都不适用于 Cli 模式。

【讨论】:

在 CLI 模式下应该在哪里输入 URL? 只是为了防止在cli模式下启动php script/inlude。在有多个开发人员的项目中很有用。【参考方案21】:
if (basename($_SERVER['PHP_SELF']) == basename(__FILE__))  die('Access denied'); ;

【讨论】:

【参考方案22】:
<?php       
$url = 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
  if (false !== strpos($url,'.php')) 
      die ("Direct access not premitted");
  
?>

【讨论】:

【参考方案23】:

已经多次提到将包含文件存储在 Web 可访问目录之外,这在可能的情况下无疑是一个好策略。但是,我还没有看到提到的另一个选项:确保您的包含文件不包含任何可运行的代码。如果你的包含文件只定义了函数和类,没有其他代码,直接访问时只会产生一个空白页。

无论如何都允许从浏览器直接访问该文件:它不会做任何事情。它定义了一些函数,但没有一个被调用,所以它们都没有运行。

<?php

function a() 
    // function body


function b() 
    // function body

这同样适用于只包含 PHP 类的文件。


尽可能将文件保存在 Web 目录之外仍然是个好主意。

您可能不小心停用了 PHP,在这种情况下,您的服务器可能会将 PHP 文件的内容发送到浏览器,而不是运行 PHP 并发送结果。这可能会导致您的代码(包括数据库密码、API 密钥等)泄露。 Web 目录中的文件占用了您可能希望用于您的应用程序的 URL。我使用的 CMS 不能有一个名为 system 的页面,因为这会与用于代码的路径冲突。我觉得这很烦人。

【讨论】:

【参考方案24】:

尽管您可以使用下面的方法,但它确实存在缺陷,因为它可以被伪造,除非您可以添加另一行代码以确保请求仅来自您的服务器,或者使用 Javascript。 您可以将此代码放在 html 代码的 Body 部分中,以便在那里显示错误。

<?
if(!isset($_SERVER['HTTP_REQUEST']))  include ('error_file.php'); 
else  ?>

在此处放置您的其他 HTML 代码

<?  ?>

这样结束,因此错误的输出将始终显示在正文部分中,如果这是您想要的那样。

【讨论】:

我知道所有的HTTP_*服务器头都是不可信的,所以你最好不要使用这种方法。【参考方案25】:

出于安全原因,我建议不要使用$_SERVER。 您可以在包含另一个变量的第一个文件中使用像 $root=true; 这样的变量。 并在包含的第二个文件的开头使用isset($root)

【讨论】:

【参考方案26】:

您还可以做的是密码保护目录并将所有 php 脚本保存在那里,当然除了 index.php 文件,因为在包含密码时不需要,因为它只需要 http使用权。它还将为您提供访问脚本的选项以防万一,因为您将拥有访问该目录的密码。您需要为目录设置 .htaccess 文件和 .htpasswd 文件来验证用户身份。

好吧,您也可以使用上面提供的任何解决方案,以防您觉得不需要正常访问这些文件,因为您始终可以通过 cPanel 等访问它们。

希望对你有帮助

【讨论】:

【参考方案27】:

最简单的方法是将包含的内容存储在 Web 目录之外。这样服务器就可以访问它们,但没有外部机器。唯一的缺点是您需要能够访问服务器的这一部分。好处是它不需要设置、配置或额外的代码/服务器压力。

【讨论】:

【参考方案28】:

我没有发现 .htaccess 的建议很好,因为它可能会阻止 您可能希望允许用户访问的该文件夹中的其他内容, 这是我的解决方案:

$currentFileInfo = pathinfo(__FILE__);
$requestInfo = pathinfo($_SERVER['REQUEST_URI']);
if($currentFileInfo['basename'] == $requestInfo['basename'])
    // direct access to file

【讨论】:

【参考方案29】:

前面提到的添加了 PHP 版本检查的解决方案:

    $max_includes = version_compare(PHP_VERSION, '5', '<') ? 0 : 1;
    if (count(get_included_files()) <= $max_includes)
    
        exit('Direct access is not allowed.');
    

【讨论】:

我不太明白这如何阻止直接访问 @adam-lindsay get_included_files 会将包含的文件作为数组返回,count 将数组更改为数字,我们包含的文件应该超过 1 个文件,如果有1个包含文件,意思是这个文件直接调用【参考方案30】:

您可以使用 phpMyAdmin 样式:

/**
 * block attempts to directly run this script
 */
if (getcwd() == dirname(__FILE__)) 
    die('Attack stopped');

【讨论】:

以上是关于防止直接访问 php 包含文件的主要内容,如果未能解决你的问题,请参考以下文章

PHP PHP - 防止直接访问php文件

php 防止直接访问主题/插件文件

php 防止直接访问主题/插件文件

防止使用浏览器 url 直接访问图像

防止直接访问由ajax函数调用的文件[重复]

防止别人下载或访问你的PHP配置文件