测试目录是否是另一个文件夹的子目录

时间:2009-10-27 04:00:12

标签: php filesystems directory

我正在尝试创建一个阻止访问某些低级目录的函数。例如,在构建我的网站时,我不希望上传的内容低于 / var / www / html / site / uploads / ,如果我在编码时犯了错误。这也有助于在删除缓存文件或其他任何内容时阻止目录删除拼写错误。

使用realpath()和strcasecmp()。

可以轻松完成

问题是我无法使用realpath()生成绝对路径,因为对具有不存在的目录的此函数的任何调用都将返回FALSE。以下是我查看验证它们的路径的最佳尝试。

function is_sub_dir($path = NULL, $parent_folder = NULL) {

    //Convert both to real paths
    //Fails if they both don't exist
    //$path = realpath($path);
    //$parent_folder = realpath($parent_folder);

    //Neither path is valid
    if( !$path OR !$parent_folder ) {
        return FALSE;
    }

    //Standarize the paths
    $path = str_replace('\\', '/', $path);
    $parent_folder = str_replace('\\', '/', $parent_folder);

    //Any evil parent directory requests?
    if(strpos($path, '/..') !== FALSE) {
        return FALSE;
    }

    //If the path is greater
    if( strcasecmp($path, $parent_folder) > 0 ) {
        return $path;
    }

    return FALSE;
}

//BAD FOLDER!!!
var_dump(is_sub_dir('/var/www/html/site/uploads/../', '/var/www/html/site/uploads/'));

有谁知道如何正确放置文件路径块以防止低级文件夹访问?

:更新:

我想进一步解释一下,这个检查方法将用于多个服务器以及创建高于给定目录的目录。

例如,在我的uploads目录中,我希望允许管理员创建新的子目录,例如 ... uploads / sub / 。通过找出一种可靠的方法来确保给定的目录实际上比父目录更高 - 我可以感觉更安全,允许我的管理员使用uploads文件夹中的文件系统。

因为我可能需要在创建之前验证上传/子是否高于上传/ 我不能使用realpath(),因为上传/ sub 不再存在。

关于PHP即时计算的uploads文件夹的实际位置。

define('UPLOAD_PATH', realpath(dirname(__FILE__)));

:更新2:

我有一个想法,如果我使用realpath比较整个路径减去最后一个目录段,该怎么办?那么即使仍然需要创建最后一个目录段 - 可以强制路径的其余部分与最小父目录匹配?

5 个答案:

答案 0 :(得分:8)

不是您当前问题的答案,但您应该从不在真实的安全环境中使用黑名单。

那是因为您可能会更改目录布局但忘记更新黑名单,使您的安全无效。

使用白名单,您可以列出允许上传到的位置。如果您随后更改了布局并忘记更改白名单,则您的安全性实际上已增加,而不是减少。而用户抗议的嚎叫会提醒你忘记。

在禁止访问较低级别目录方面,我认为获得真实路径是一件简单的事情(所有./../\\转换为标准化形式)然后,如果它以/var/www/html/site/uploads/开头,则在此之后再有/个字符时失败。

之前我已完成此规范化,它基本上由(来自内存):

组成
  1. 将所有\\替换为/(因此它使用UNIX-isms)。
  2. 如果它不以/开头,请在前面添加基本目录(因此它始终是绝对路径)。
  3. 将所有/./替换为/(摆脱无用的“留在当前目录中”移动)。
  4. 将所有/X/../替换为/,其中X是任何非/字符(摆脱目前的目标移动)。
  5. 然后确保它是有效的位置。
  6. 剩下的内容通常可以安全使用,但可能存在边缘情况,具体取决于是否有更多目录移动命令可用(我已将...视为../..的等价)。您的里程可能会有所不同。

答案 1 :(得分:4)

我所知道的最好的方法是简单地使除了上传目录之外的所有内容都由Web服务器运行的用户不可写。例如,在Debian上,Apache以用户www-data运行。因此,我确保Apache可能提供的所有目录都是可读/可执行的,由www-data以外的某个用户拥有,并且只能由该用户(或某个管理员组)写入。然后,如果某个目录需要由Web服务器写入,我可以通过组{0}使其www-data可写。

如果可能的话,将可写目录移到主服务器树之外也是个好主意。如果我控制服务器,为此明确地在/var/lib下创建一个目录。如果我不控制服务器,我把它放在服务目录旁边。例如,使用您的/var/www/site示例,我会添加另一个级别,例如/var/www/site/html(针对直接提供的HTML和顶级脚本),/var/www/site/scripts(针对include() ed PHP脚本)和/var/www/site/data(用于上传的数据)。当然,如果您需要提供上传的数据,您需要编写一个PHP包装器来提供它们或将它们放在/var/www/site/html/uploads下,类似于您现在的工作方式。

答案 2 :(得分:1)

即使路径中的最后一个目录尚不存在,以下代码仍然有效。任何犯规或额外丢失的目录都会返回false。

此功能的限制是它只适用于(大部分)已存在的路径和使用标准英文字符(/.hts /,/ files.90.3r3 /,/ my_photos /)等的目录名。) / p>

function is_sub_dir($path = NULL, $parent_folder = SITE_PATH) {

    //Get directory path minus last folder
    $dir = dirname($path);
    $folder = substr($path, strlen($dir));

    //Check the the base dir is valid
    $dir = realpath($dir);

    //Only allow valid filename characters
    $folder = preg_replace('/[^a-z0-9\.\-_]/i', '', $folder);

    //If this is a bad path or a bad end folder name
    if( !$dir OR !$folder OR $folder === '.') {
        return FALSE;
    }

    //Rebuild path
    $path = $dir. DS. $folder;

    //If this path is higher than the parent folder
    if( strcasecmp($path, $parent_folder) > 0 ) {
        return $path;
    }

    return FALSE;
}

答案 3 :(得分:-2)

这样容易吗?

const UPLOAD_DIR = '/var/www/site/uploads/';

每次你需要使用它时你只需使用常量,因为它听起来只有一个你可以上传的地方。这样你就不会轻易搞砸了。

if ($dir != UPLOAD_DIR) {
   // No access; Error
}

有时为了保护自己,你必须保持警惕。只需确保在所有文件访问之前调用is_sub_dir()。

修改

现在问题更清楚了,我看到我的答案毫无意义。 =)我唯一的其他建议是重申其他人所说的:消毒,消毒,消毒。

答案 4 :(得分:-2)

我知道它已经被回答了,但最简单的解决办法是不允许在路径中使用“..”:

if(realpath(dirname($file)) == dirname($file)){ // OK! }