Change record status: 
Project: 
Introduced in branch: 
6.x, 7.x
Introduced in version: 
6.35, 7.35
Description: 

Many areas of Drupal use a "destination" query string parameter for built-in redirect functionality. For example, submitting the login form at a URL such as http://example.org/?destination=admin will automatically redirect the user to http://example.org/admin after login.

In many cases, external URLs passed through the "destination" parameter (for example, http://example.org/?destination=http://example.com) were already forbidden; Drupal would not use them for the redirect. However, because several areas of Drupal core as well as common patterns used in contributed modules did not correctly prevent external URLs from being used in this case and were therefore subject to an open redirect vulnerability (see SA-CORE-2015-001), Drupal 6.35 and Drupal 7.35 now completely prevent the "destination" parameter from containing an external URL. If one is provided, it will be removed early in the Drupal bootstrap and not used.

If your site does not rely on or does not expect redirects to external websites using URL query parameters, this change will not affect you.

However, if your site is relying on passing external URLs for redirect purposes via a query parameter, you should switch to using a different URL query parameter (for example, "destination-url") and to avoid an open redirect vulnerability, make sure to validate it before redirecting to the provided URL:

  • For example, if you only want to allow redirects to a known, safe list of domains, use code similar to the following:
      if (isset($_GET['destination-url']) && url_is_external($_GET['destination-url'])) {
        $parsed_url = parse_url($_GET['destination-url']);
        // Only allow redirects to example.com and example.org.
        if (!isset($parsed_url['host']) || !in_array($parsed_url['host'], array('example.com', 'example.org'))) {
          // Remove the parameter since it's not safe to redirect to.
          unset($_GET['destination-url']);
        }
      }
    

    In Drupal 6, replace url_is_external() with menu_path_is_external() in the above code.

  • Alternatively, to allow the code which generates the link to decide what domains are safe to redirect to, use a security token to validate that the link came from a trusted source. When generating the link, use code similar to the following (assuming example.com is a trusted domain that is safe to redirect to):
      $link_text = t('Click here to go to the "node" page on this site and be redirected to http://example.com afterwards.');
      $link = l($link_text, 'node', array('query' => array('destination-url' => 'http://example.com', 'destination-url-token' => drupal_get_token('http://example.com'))));
    

    And then before performing the redirect:

      if (isset($_GET['destination-url']) && url_is_external($_GET['destination-url'])) {
        if (!isset($_GET['destination-url-token']) || !drupal_valid_token($_GET['destination-url-token'], $_GET['destination-url'])) {
          // Remove the parameter since it did not come from a trusted source and
          // is not safe to redirect to.
          unset($_GET['destination-url']);
        }
      }
    

    In Drupal 6, replace url_is_external() with menu_path_is_external() in the above code.

If it is necessary for a site to pass external URLs for redirect purposes via the "destination" parameter (because incoming links from another site rely on it and cannot be changed) this could be accomplished by putting code in settings.php to grab it from $_GET['destination'], validate it there using the above method, and then store it somewhere else - this will run before Drupal core removes it from $_GET['destination'].

Impacts: 
Site builders, administrators, editors
Module developers