注意:要阅读这篇文章,我们假设您具有最低限度的 PHP 编程知识。
这篇文章是关于 PHP 代码片段的,您可能在您最喜欢的 CMS 或框架的顶部看到过该代码片段,并且您可能已经读过,为了安全起见,您应该始终将其包含在您所使用的所有 PHP 文件的标头中。发展,尽管没有非常清楚地解释原因。我指的是这段代码:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
这种类型的代码在 WordPress 文件中很常见,尽管它实际上出现在几乎所有框架和 CMS 中。例如,在 CMS Joomla 的情况下,唯一改变的是使用 JEXEC,而不是 ABSPATH。否则,逻辑是相同的。这个 CMS 源于另一个名为 Mambo 的 CMS,它也使用类似的代码,但使用 _VALID_MOS 作为常量。如果我们追溯到更远的时间,我们会发现第一个使用这种类型代码的 CMS 是 PHP-Nuke(被一些人认为是第一个 PHP CMS)。
PHP-Nuke(以及当今大多数 CMS 和框架)的执行流程包括顺序加载多个文件,这些文件一起响应用户或访问者在网络上执行的操作。也就是说,想象一下当时的网站,位于 example.net 域下并安装了此 CMS。每次加载主页时,系统都会有序地执行一系列文件(这里只是一个示例,而不是真正的序列):index.php => load_modules.php =>;模块.php。也就是说,按照这个顺序,首先加载index.php,然后该脚本加载load_modules.php,然后加载modules.php。
这个执行链并不总是从第一个文件(index.php)开始。事实上,任何人都可以通过直接通过 URL 调用其他 PHP 文件之一(例如 http://example.net/load_modules.php 或 http://example.net/modules.php)来跳过此流程的一部分,这正如我们将看到的,在许多情况下可能是危险的。
这个问题是如何解决的?引入了安全措施,在每个文件的开头添加代码,类似于:
<?php if (!eregi("modules.php", $HTTP_SERVER_VARS['PHP_SELF'])) { die ("You can't access this file directly..."); }
基本上,这段代码位于一个名为modules.php 的文件的标头中,用于检查是否可以通过URL 直接访问modules.php。如果是这样,执行将停止,并显示消息:“您无法直接访问此文件...”。如果$HTTP_SERVER_VARS['PHP_SELF']不包含modules.php,那么这意味着我们处于正常的执行流程中并被允许继续。
但是,此代码有一些限制。首先,插入的每个文件的代码都不同,这增加了复杂性。另外,在某些情况下,PHP 没有为 $HTTP_SERVER_VARS['PHP_SELF'] 赋值,这限制了其有效性。
那么开发者做了什么?他们用更简单、更高效的版本替换了所有这些代码片段:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
在 PHP 社区中已经很常见的这段新代码中,验证了常量的存在。该常量在流程的第一个文件(index.php 或 home.php 或一些类似文件)中定义并分配了一个值。因此,如果该常量不存在于流中的其他文件中,则意味着有人跳过了 index.php 文件并尝试直接访问另一个文件。
此时你肯定会想,打破执行链一定是世界上最严重的事情。然而,现实情况是,通常,它并不代表重大危险。
当 PHP 错误暴露我们文件的路径时,可能会出现危险。如果我们在服务器上配置了错误抑制,那么我们就不必担心,并且即使没有隐藏错误,暴露的信息也很少,只为可能的攻击者提供一些线索。
也可能有人访问包含 HTML 片段(来自视图)的文件,从而泄露其部分内容。在大多数情况下,这也不应该令人担忧。
最后,开发人员可能由于疏忽或缺乏经验而在执行流程中插入没有外部依赖的危险代码。这是非常不寻常的,因为通常框架或 CMS 的代码依赖于其他类、函数或外部变量来执行。因此,如果您尝试直接通过 URL 运行脚本,它将无法找到这些依赖项,并且不会继续执行。
那么,如果几乎没有任何值得担心的地方,为什么要添加常量代码呢?原因是这样的:“此方法还可以防止通过攻击 注册全局变量 进行意外的变量注入,从而防止 PHP 文件假设它实际上不在应用程序内部。”
自 PHP 诞生以来,所有通过 URL (GET) 或表单 (POST) 发送的变量都会自动转换为全局变量。也就是说,如果文件 download.php?filepath=/etc/passwd 被访问,则在 download.php 文件中(以及在执行流程中依赖于该文件的文件中)可以使用 echo $filepath ;结果将是 /etc/passwd。
在 download.php 中,无法判断 $filepath 变量是否是由执行流程中的前一个文件创建的,或者是否有人通过 URL 或 POST 欺骗了它。这产生了很大的安全漏洞。让我们看一个例子,假设 download.php 文件包含以下代码:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
开发人员可能考虑过使用前端控制器模式来实现他的代码,即让所有 Web 请求都通过单个输入文件(index.php、home.php 等)。该文件将负责初始化会话、加载公共变量,最后将请求重定向到特定脚本(在本例中为 download.php)以下载文件。
但是,攻击者可以通过简单地调用 download.php?filepath=/etc/passwd 来绕过计划的执行序列,如前所述。因此,PHP 会自动创建全局变量 $filepath,其值为 /etc/passwd,从而允许攻击者从系统下载该文件。严重错误。
这只是冰山一角,因为甚至可以用最小的努力进行更危险的攻击。例如,在如下代码中,程序员可以将其保留为未完成的脚本:
<?php if (!eregi("modules.php", $HTTP_SERVER_VARS['PHP_SELF'])) { die ("You can't access this file directly..."); }
攻击者可以使用远程文件包含 (RFI) 攻击执行任何代码。因此,如果攻击者在自己的网站 https://mysite.net 上创建了一个 My.class.php 文件,其中包含他想要执行的任何代码,他就可以通过将其域传递给易受攻击的脚本来调用该脚本:codigo_inutil.php?base_path= https://mysite.net,攻击完成。
另一个示例:在名为remove_file.inc.php 的脚本中,包含以下代码:
<?php if (!defined('MODULE_FILE')) { die ("You can't access this file directly..."); }
攻击者可以使用像remove_file.inc.php?filename=/etc/hosts这样的URL直接调用这个文件,从而尝试从系统中删除/etc/hosts文件(如果系统允许的话,或者其他)您有删除权限的文件)。
在像 WordPress 这样内部也使用全局变量的 CMS 中,这种类型的攻击是毁灭性的。然而,由于不断的技术,这些和其他 PHP 脚本受到了保护。让我们看最后一个例子:
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
现在,如果有人尝试访问remove_file.inc.php?filename=/etc/hosts,该常量将阻止访问。它必须是一个常量,因为如果它是一个变量,攻击者当然可以注入它。
此时您可能想知道如果 PHP 如此危险,为什么还要保留此功能。另外,如果你了解其他脚本语言(JSP、Ruby 等),你会发现它们没有类似的东西(这就是为什么它们也不使用常量技术)。让我们记住,PHP 是作为 C 语言的模板系统诞生的,这种行为促进了开发。好消息是,看到它造成的问题,PHP 维护者决定在 php.ini 中引入一个名为 register_globals 的指令(默认激活)以允许禁用此功能。
但由于问题仍然存在,他们默认禁用它。即便如此,许多主机仍然继续启用它,因为担心客户的项目会停止工作,因为当时的大部分代码没有使用推荐的 HTTP_*_VARS 变量来访问 GET/POST/... 值,但是而是全局变量。
最后,看到情况没有改变,他们做出了一个重大决定:在 PHP 5.4 中消除这个功能,以避免所有这些问题。因此,如今,像我们看到的脚本(不使用常量)不再通常构成危险,除了在某些情况下出现一些无害的警告/通知。
时至今日,持续技术仍然很常见。然而,令人悲伤的是——也是导致这篇文章的原因——很少有开发人员知道其使用的真正原因。
与过去的其他良好实践一样(例如将函数中的参数复制到局部变量以避免调用中引用的危险,或者在私有变量中使用下划线来区分它们),许多人仍然应用它只是因为有人曾经告诉他们这是一个很好的做法,而没有考虑它是否真的在当今时代增加了价值。现实情况是,在大多数情况下,不再需要此技术。
这种做法失去相关性的一些原因如下:
*register 全局变量的消失:从 PHP 5.4 开始,将 GET 和 POST 变量注册为 PHP 全局变量的功能不再存在。正如我们所看到的,如果没有 *register globals,单个脚本的执行就变得无害,消除了这种做法的主要原因。
当前代码中更好的设计:即使在 PHP 5.4 之前的版本中,现代代码也经过更好的设计,在类和函数中结构化,这使得通过外部变量进行访问或操作变得复杂。即使是经常使用全局变量的 WordPress,也可以将这些风险降到最低。
*front-controllers的使用:如今,大多数Web应用程序都使用精心设计的*front-controllers,这确保了类和函数的代码仅当执行链从主入口点开始时才被执行。因此,如果有人尝试单独上传文件并不重要:如果流程没有从正确的点开始,逻辑将不会激活。
类自动加载:由于当前开发中使用了类自动加载,因此大大减少了 include 或 require 的使用。这意味着,除非您是新手开发人员,否则不应存在可能带来风险的 includes 或 requires(例如 远程文件包含 或 本地文件包含)。
公共和私有代码的分离:在许多现代CMS和框架中,公共代码(例如资产)与私有代码(编程代码)分离。此措施特别有价值,因为它可以确保如果 PHP 在服务器上失败,PHP 文件中的代码(无论它们是否使用常量技术)不会暴露。尽管这并不是专门为了缓解注册全局变量而实施的,但它有助于避免其他安全问题。
友好 URL 的扩展使用:如今,将服务器配置为使用友好 URL 很常见,这总是强制使用单个入口点进行编程。这使得任何人几乎不可能单独上传 PHP 文件。
抑制生产中的错误输出:大多数现代 CMS 和框架默认都会阻止错误输出,因此攻击者无法找到有关应用程序内部工作原理的线索,这可能会促进其他类型的错误输出攻击。
尽管在大多数情况下不再需要此技术,但这并不意味着它永远没有用处。作为一名专业开发人员,必须分析每种情况并确定持续技术是否与您工作的特定环境相关。这是您应该始终遵循的标准,即使在您认为良好的实践中也是如此。
如果您仍然不确定何时应用持续技术,这些建议可以指导您:
对于其他一切,如果您有疑问,请应用它。在大多数情况下,它不应该是有害的,并且可以在意外情况下保护您,特别是如果您刚刚开始。随着时间和经验的积累,您将能够更好地评估何时应用此技术和其他技术。
以上是框架和 CMS 中那些奇怪的 PHP 代码的详细内容。更多信息请关注PHP中文网其他相关文章!