【问题标题】:Preventing Infinite Redirects in Access Control Library防止访问控制库中的无限重定向
【发布时间】:2012-07-21 21:44:08
【问题描述】:

我正在开发一个名为 PHP-Bouncer 的 PHP 开源基于角色的访问控制库。 PHP-Bouncer 允许用户定义角色列表,每个角色提供访问哪些页面,并且每个角色还可以定义覆盖其他页面的页面列表(因此转到被覆盖的页面会将您重定向到覆盖页面) .这是一个如何工作的示例(来自文档中的Access Managed Example):

$bouncer = new Bouncer();
// Add a role     Name,      Array of pages role provides
    $bouncer->addRole("Public", array("index.php", "about.php", "fail.php"));
// Add a role          Name,              Array of pages role provides
    $bouncer->addRole("Registered User", array("myaccount.php", "editaccount.php", "viewusers.php"));
// Add a role          Name,   Array of pages role provides       List of pages that are overridden by other pages
    $bouncer->addRole("Admin", array("stats.php", "manageusers.php"), array("viewusers.php" => "manageusers.php"));

// Here we add some users. The user class here extends the BouncerUser class, so it can still do whatever you
// would normally create a user class to do..
    $publicUser         = new User();
    $registeredUser     = new User();
    $adminUser          = new User();
    $registeredAndAdmin = new User();

    $publicUser->addRole("Public");

    $registeredUser->addRole("Public"); // We add the public group to all users since they need it to see index.php
    $registeredUser->addRole("Registered User");

    $adminUser->addRole("Public"); // We add the public group to all users since they need it to see index.php
    $adminUser->addRole("Admin");

    $registeredAndAdmin->addRole("Public"); // We add the public group to all users since they need it to see index.php
    $registeredAndAdmin->addRole("Registered User");
    $registeredAndAdmin->addRole("Admin");

    $bouncer->manageAccess($publicUser->getRoles(), substr($_SERVER["PHP_SELF"], 1), "fail.php");

这是我遇到的问题:在您在上面看到的 manageAccess 函数中,只要角色定义合理,并且所有用户都可以访问 fail.php(或者 fail.php 没有实现 $bouncer目的)。一旦有人创建了一个具有内在冲突(例如用自身覆盖页面)或未能让所有用户访问失败页面的角色,manageAccess 函数就会导致无限循环。由于这很糟糕,我想修复它。但是,我不确定在防止无限循环的同时,允许一些重定向的最佳方法是什么(重定向最多两次或三次可能是理想的行为是可行的)。这里是 manageAccess 函数:

/**
 * @param array  $roleList
 * @param string $url
 * @param string $failPage
 */
public function manageAccess($roleList, $url, $failPage = "index.php"){
    $granted = false;
    foreach($roleList as $role){
        if(array_key_exists($role, $this->roles)){
            $obj = $this->roles[$role];
            /** @var $obj BouncerRole */
            $response = $obj->verifyAccess($url);
            if($response->getIsOverridden()){ // If access to the page is overridden forward the user to the overriding page
                $loc            = ($obj->getOverridingPage($url) !== false) ? $obj->getOverridingPage($url) : $failPage;
                $locationString = "Location: ".$loc;
                header($locationString);
                // I broke something in the last commit, perhaps this comment will help?
            }
            if($response->getIsAccessible()){ // If this particular role contains access to the page set granted to true
                $granted = true; // We don't return yet in case another role overrides.
            }
        }
    }
    // If we are here, we know that the page has not been overridden
    // so let's check to see if access has been granted by any of our roles.
    // If not, the user doesn't have access so we'll forward them on to the failure page.
    if(!$granted){
        $locationString = "Location: ".$failPage."?url=".urlencode($url)."&roles=".urlencode(serialize($roleList));
        header($locationString);
    }
}

有什么建议吗?

【问题讨论】:

  • 你应该在第一个header($locationString)之后添加return。
  • 这会被执行吗?
  • 如果不返回,则可能会发送多个Location 标头。
  • 经过一番研究,我发现您在发送位置标头后需要终止是正确的。我选择使用 exit();而不是返回,因为它会阻止页面的其余部分被返回,这是我想要的行为。但是,我真正需要的是一种方法来确保重定向的数量不会失控。如果您尝试将某人重定向到 index.php 而不授予他们访问 index.php 的权限(导致库将他们重定向到失败页面,该页面恰好是 index.php),就会发生这种情况。这更有意义吗?

标签: php infinite-loop access-control rbac


【解决方案1】:

由于您想要的功能是允许“一些重定向”,我能想到的最好的方法是创建一个$_SESSION(或$_GET我想会工作)参数每次发出重定向时都会增加。我应该指出,从用户的角度来看,这会很烦人,但这是您的网站。

假设我们将$_SESSION 参数命名为num_redirects:

  1. 在我们重定向之前,检查是否设置了$_SESSION['num_redirects']。如果不是,请将其设置为0。
  2. 获取$_SESSION['num_redirects']的当前值,看是否高于重定向阈值。
  3. 如果超过阈值,不重定向,直接die();
  4. 如果不高于阈值,则递增$_SESSION['num_redirects'],将其存储回来,然后重定向。

因此,该代码将如下所示(我还通过调用 http_build_query() 改进了您的 URL 查询字符串:

某处,我们需要定义阈值:

define( 'MAX_NUM_REDIRECTS', 3);

那么,我们可以这样做:

// Assuming session_start(); has already occurred
if(!$granted) {
    if( !isset( $_SESSION['num_redirects'])) {
        $_SESSION['num_redirects'] = 0;
    }

    // Check if too many redirects have occurred
    if( $_SESSION['num_redirects'] >= MAX_NUM_REDIRECTS) {
        die( "Severe Error: Misconfigured roles - Maximum number of redirects reached\n");
    }

    // If we get here, we can redirect the user, just add 1 to the redirect count
    $_SESSION['num_redirects'] += 1;

    $query_string = http_build_query( array(
        'url' => $url,
        'roles' => serialize($roleList)
    ));
    $locationString = "Location: " . $failPage . '?' . $query_string;
    header($locationString);
    exit(); // Probably also want to kill the script here
}

就是这样!请注意,您必须为第一次调用header() 添加相同的逻辑。如果您要在其他地方重复此功能,则可能值得将重定向封装在辅助函数中:

function redirect( $url, array $params = array()) {
    if( !isset( $_SESSION['num_redirects'])) {
        $_SESSION['num_redirects'] = 0;
    }

    // Check if too many redirects have occurred
    if( $_SESSION['num_redirects'] >= MAX_NUM_REDIRECTS) {
        die( "Severe Error: Maximum number of redirects reached\n");
    }

    // If we get here, we can redirect the user, just add 1 to the redirect count
    $_SESSION['num_redirects'] += 1;

    $query_string = http_build_query( $params);
    $locationString = "Location: " . $url . ( !empty( $query_string) ? ('?' . $query_string) : '');
    header($locationString);
    exit(); // Probably also want to kill the script here
}

那么,你可以这样称呼它:

if(!$granted){
    redirect( $failPage, array(
        'url' => $url,
        'roles' => serialize($roleList)
    ));
}

通过这种方式,您可以将重定向所需的所有逻辑封装在一个函数中。

【讨论】:

  • +1 表示彻底性、关于 http_build_query 的提示和重定向功能。不过,我确实担心在库中使用会话变量。由于我不知道将来谁会使用这个库(我希望它足够有用,人们想要使用它),我担心我可能会通过要求会话来干扰某人的设置。这是我的偏执狂,还是合理的担忧?
  • 您始终可以默认为$_GET 变量,它保证是交叉兼容的。这取决于您的目标受众 - 如果您认为您的用户可能不需要会话(并且没有可用的会话),则忽略它并使用 $_GET(请记住,用户可以更改它)。然而,随着 Web 技术的进步,我相信不使用某种服务器端数据持久性的情况越来越少。需要会话是很常见的,特别是考虑到 PHP 默认启用会话扩展。
  • 我认为处理这个问题的最佳方法是使其可配置。我创建了一个功能类似于枚举的类,并让用户在 BouncerProtectionMethod::Session、BouncerProtectionMethod::Get 和 BouncerProtectionMethod::None 之间进行选择。我还为 get 或 session 变量将被调用的内容创建了一个变量(以防用户想要在其他地方跟踪它或已经在跟踪类似的东西),以及一个用于最大重定向的变量。
  • 另外,由于这是一个开源项目,如果您愿意,请随时发送加入请求,我们随时欢迎帮助!
猜你喜欢
  • 2021-03-15
  • 2012-08-10
  • 1970-01-01
  • 2011-02-04
  • 1970-01-01
  • 1970-01-01
  • 2017-03-17
  • 2014-06-01
  • 2010-09-18
相关资源
最近更新 更多