Safely Redirecting with an Open URL Parameter in PHP

One of the ways in which phishing e-mails get around e-mail client's filters is to use a link to a trusted site that has an unchecked redirect URL. What I mean by a redirect URL is a page on a website that takes a URL as a parameter, and forwards requests for that page to the URL provided.

Take this link for example. It links to Codemasters web site, but will bring you straight back to this page, while that example is harmless, consider this.

If I was a scammer, and I wanted to steal your Codemasters login details. I would create an exact clone of the Codemasters site, send out millions of e-mails with a link that uses the above redirect to send you to the fake site. The e-mail would appear to come from Codemasters, the link would point to the Codemasters site and redirect to a fake site, say codemasters.org, that isn't owned by Codemasters. When you enter your details, I would e-mail them to me, then redirect you back to the official Codemasters site to process the login. I would now have access to your Codemasters account.

This article will discuss methods for performing open re-directs safely using PHP. This is more theory although code is supplied for the first example. You can apply this to any other server-side scripting language, such as ASP or JSP etc.

And finally, something not to do: Validating the referrer HTTP variable

Using a Database of Trusted URLs

Possibly the most secure way to ensure that your redirect script is only redirecting to URLs you specify is to list those URLs in a database table. When the redirect page is loaded, it would then check the database to ensure the URL is valid, and then perform the redirect, otherwise display an error or redirect to your home page etc.

The code below shows how you would achieve this with PHP, for simplicity I've used an array of trusted URLs rather than a database table, but where you store the trusted URLs is irrelevant for this article (so long as it's secure). :-)

<?php
    $trusted_urls = array( 'http://www.fedoraproject.org/wiki/',
                           'http://www.fedoraforum.org/',
                           'http://www.redhat.com/' );
    // DO NOT forget to use mysql_real_escape_string if you're sending
    // the URL to a database.
    //        mysql_real_escape_string(trim($_GET['to']), $link);
    $to_url = trim($_GET['to']);
    if (in_array($to_url, $trusted_urls))
    {
        header('HTTP/1.0 302 Moved Temporarily');
        header("Location: {$to_url}");
        exit;
    } else {
        echo "The redirect URL passed is not a trusted URL, ".
             "redirecting denied.";
        exit;
    }
?>

The advantage of this method is that it is damned near impossible to trick, provided the 'to' URL parameter is properly sanitised. The disadvantage to this is it's high maintenance. You could however create a separate script to ensure that all links on your pages redirect correctly and return a list of URLs that don't which you can then add to your trusted URLs list.

This all seems like too much work for me, so I started thinking about another method that can be used.

Using a 'From' Parameter in the URL

This is the method SavageReactor uses. When linking to your redirect script, as well as providing the destination to redirect to, you also provide the source of the redirect. The redirect script will then open this page, and ensure that the destination URL exists within it, verifying that your site is sending the redirect.

This is my preferred method because it requires the least maintenance. The list of trusted domains maintains itself as and when you modify a link on a page and it cannot be tampered with.

The downside to this approach is that it requires yet more database access, potentially making your web site run slower. With modern servers, this is really a non-issue. Another downside is that if you move the page, you'll need to update all of the 'From' fields for all the redirects on the page. This can be worked around by using a %CURRENT_URL% field in your HTML, and using PHP to replace that with the current URL. Again this uses even more resources.

This is how this would work:

  1. Get the destination and source URLs
  2. Verify the source URL is on your domain
  3. Retrieve the source URL (from the database, or a HTTP request)
  4. If the destination URL exists within the source URL's HTML code, allow the redirect
  5. If it doesn't, send them packing

Using the Site Log to Verify the User Visited Your Site

If like SavageReactor, you keep a site log in a database, this can be used to verify that the IP address requesting the URL has previously accessed other pages on your site, a requirement if they are going to click a redirect link on your site. By adding a two hour expiration time to this, an IP address will only be able to redirect from your site within those two hours, after that the redirect link will no longer work until that IP has viewed or refreshed another page on your site.

Obviously when a user clicks a link in an e-mail, their IP address won't be logged as viewing pages, but going straight to the redirect page, causing the redirect to fail.

Initially I started using this on SavageReactor as a method to lure potential phishers into using SavageReactor, as to them (or a bot) the redirect page would appear to work for any URL (for up to 2 hours of them being on SavageReactor), but any phishing e-mails they send using SavageReactor would fail, and the victim to be would then know they were about to have been conned.

Unfortuantely this method does have one major setback: Some companies use multiple proxies. What this means is that the request to the redirect page could come from a different IP address than the request for the page containing the redirect. This would result in false positives and stop valid users from clicking links to external sites.

Validating the HTTP Referrer Variable

The HTTP referrer variable is used to tell the current page where you came from. For example, if you came to this page from Google, Google would be in the referrer variable.

Since all redirects from your site should come from your site, it is reasonable to say that the referrer field should contain a URL from your site. The downside of this is that not all browsers pass the referrer to the next page. Some proxy servers also remove or change the referrer field, so this cannot be safely relied on.

Also consider that a phishing link from a e-mail client probably won't have a referrer field either. In short, do not use the HTTP referrer to validate redirects.

Conclusion

Open redirects are extremely useful for seeing where your users are going, especially if you advertise on your site. It allows you to see what they are interested in that you do not have, and decide whether to add that content.

Open redirects do pose a security risk. Not to your site as such, but to the thousands of users out there who are conned by phishing scams. I see it as a webmasters responsibilty to try to minimize the amount of online fraud.

When choosing one of the above methods to use, it really boils down to this question:

Would you prefer higher maintenance and lower resource usage on your server?

If you answer yes to that, then the first method is for you. If you prefer less maintenance, then the second method should be your choice.