PHP `require_once` 包含错误的文件

PHP `require_once` includes wrong file

我在 Linux Ubuntu 14.04-LTS 机器上有一个开发树,有三个相同的分支:

main -+-- leonardo --- project --- htdocs -+- panel --- index.php
      |                                    |
      |                                    +- config.php
      |
      +-- federico --- project --- htdocs -+- panel --- index.php
      |                                    |
      |                                    +- config.php
      |
      +-- carlo ------ project --- htdocs -+- panel --- index.php
      |                                    |
      |                                    +- config.php
    ..... (you get my drift).

没有软链接也没有硬链接config.php 文件在 svn-ignore 中并且在所有分支之间是不同的

有一个Apache服务器,每个开发者都有一个virtualHost,所以我可以在http://leonardo.project.local or Federico's at http://federico.project.local看到我的开发版本。

在调查当前的怪事时,这两个文件是:

<?php // this is panel/index.php
    echo "I am " . __FILE__ . "\n";
    echo "I will include " . realpath('../config.php') . "\n";
    require_once '../config.php';

<?php // this is config.php
    echo "I am " . __FILE__ . "\n";
    exit();

预期的输出当然是:

I am leonardo/project/htdocs/panel/index.php
I will include /var/www/main/leonardo/project/htdocs/config.php
I am leonardo/project/htdocs/config.php

但实际输出是:

I am leonardo/project/htdocs/panel/index.php
I will include /var/www/main/leonardo/project/htdocs/config.php
I am federico/project/htdocs/config.php

更奇怪的是

    echo "I will include " . realpath('../config.php') . "\n";
    require_once realpath('../config.php');

有效。

TL;DR require_oncerealpath 不同意 '../config.php' 的实际位置。

真正奇怪的是,我没有看到 如何 运行ning 在 leonardo/project/htdocs/panel/ 中的脚本 知道 关于 federico/project/htdocs/config.php;它应该向上移动四个目录,然后探索很多子目录。

我几乎开始怀疑这可能与文件系统甚至内核相关。

文件系统是ext4,内核是3.13.0-55-generic #92-Ubuntu SMP Sun Jun 14 18:32:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux。该机器是最新 VMware Workstation 上的虚拟 x64。

支票

值得检查一下 include_path 的设置(这可以使用 get_include_path 完成)。

requireinclude 将在给定绝对路径和相对路径的情况下表现不同。当你使用

require_once realpath('../config.php');

这是在做:

require_once '/var/www/main/leonardo/project/htdocs/config.php';

如您所愿。

以下怪事:

require_once '../config.php';

发生是因为 PHP 将检查包含路径中的每个条目以查找匹配文件,并 return 检查第一个匹配条目。因此,很可能首先检查了 federico 配置的路径。

您是否检查过任何潜在的操作码缓存及其设置? 过去我在那里遇到过一些问题,例如没有检测到更改的文件。

具体来说,如果 opcache.use_cwd 设置设为零,这种情况可能并且 发生。

opcache.use_cwd boolean

If enabled, OPcache appends the current working directory to the script key, thereby eliminating possible collisions between files with the same base name. Disabling this directive improves performance, but may break existing applications.

如果发生这种情况,那么第一个用户或php单元脚本访问给定名称的文件不同的目录(例如 leonardo/config.phpfederico/config.php)将 "prime" 缓存该文件。 realpath 等文件系统功能不会受到影响,将继续工作。使用绝对路径的引用将继续工作。 相对路径的引用将以一种非常隐蔽的方式被破坏

因为在只有一个人工作之前,那个人的缓存已准备好满足他的需要,并且不会注意到任何事情。然后回来工作,你开始加载他的文件。

附带说明一下,该设置可能会导致无意的信息泄露,因为该设置是系统范围。所以你知道你的 ISP 有一个损坏的 use_cwd,你知道另一个站点包含 '../inc/credit_cards.php',你在 你的 站点并包含一个同名文件。 get_defined_vars() 可能让您 pwn 其他站点的登录或数据库系统。 (还没有检查,但考虑到发生的事情,不明白为什么不)。

更新

我检查了 PHP5.6 上的上述配置和一个旧的备份 VM(我们有过去五年的时间机器快照:-))。 我确实能够读取我无权访问的不同虚拟主机中所有定义的全局变量,覆盖身份验证。 This problem was discovered in 2016 但他们想到的测试并没有扩展到通过创建同名文件来欺骗 opcache 包含其他人的文件。

我也在我们当前的开发虚拟机上重新运行相同的测试,问题似乎已经消失,即使配置已经改变我不确定它们是否具有可比性。

我正在考虑将共享主机 config.inc.php 文件重命名为 config.a72b1qTy.inc.php.