Preventing Malicious Redirects

If your site permits open redirects, you may be unknowingly helping attackers take advantage of your user base.

Risks

Prevalence Common
Exploitability Easy
Impact Worrying

Redirects are a useful function to have when building a website. If a user attempts to access a resource before they are logged in, it is conventional to redirect them to the login page, put the original URL in a query parameter, and after they have logged in, automatically redirect them to their original destination. This type of functionality shows you are putting thought into the user experience, and is to be encouraged. However, you need to be sure anywhere you do redirects, they are done safely – otherwise you are putting your users in harm’s way by enabling phishing attacks.

Modern web-mail services are very good at spotting spam and other types of malicious messages. One detection method they use is to parse the out-bound links in HTML emails. These links are compared to a black-list of banned domains; if the domain is deemed to be malicious, the email is redirected to the junk folder.

This is why spammers and phishers find open redirects so enticing. If they can “bounce” a user off your website (an apparently valid domain), their messages are less likely to be marked as malicious. If the user clicks on the link, they will see your website in the link, but they will end up at whatever site the attacker wants to direct them to. A confused user might download malware or worse, because of the trust they put in your site!

Protection

Disallow Offsite Redirects

You can prevent redirects to other domains by checking the URL being passed to the redirect function. Make sure all redirect URLs are relative paths – i.e. they start with a single / character. (Note that URLs starting with // will be interpreted by the browser as a protocol agnostic, absolute URL – so they should be rejected too.)

If you do need to perform external redirects, consider restricting the individual sites that you permit redirects to.

Check the Referrer When Doing Redirects

Redirects to URLs passed in query parameters should only be triggered by pages on your site. Any other sites triggering a redirect should be treated with extreme suspicion. As a second layer of defense, check that the Referer in the HTTP request matches your domain whenever you perform a redirect.

Code Samples

The code samples below demonstrate how to check that a URL is a relative path.

Validate for relative paths with:


import re

def is_relative(url):
  return re.match(r"^\/[^\/\\]", url)

Validate for relative paths with:


def is_relative(url)
  url =~ /^\/[^\/\\]/
end

A redirect like the following is open to abuse:


protected void doGet(HttpServletRequest request, HttpServletResponse response) {
  String url = request.getParameter("url");
  if (url != null) {
    response.sendRedirect(url);
  }
}

Make sure you validate the url parameter wherever you invoke the sendRedirect method:


protected void doGet(HttpServletRequest request, HttpServletResponse response) {
  String url = request.getParameter("url");
  if (url != null && isRelative(url)) {
    response.sendRedirect(url);
  }
}

// Allow anything starting with "/", except paths starting
// "//" and "/\".
private boolean isRelative(String url) {
  return url.matches("/[^/\\]?.*");
}

Validate URLs with the following snippet:


private bool IsRelativePath(string url)
{
   if (string.IsNullOrEmpty(url))
   {
      return false;
   }
   else
   {
      if (url.Length == 1) return true;

      // Allow anything starting with "/", except paths starting
      // "//" and "/\". Allow the "~/" syntax too.
      return (url[0] == '/' && (url[1] != '/' && url[1] != '\\')) ||
             (url[0] == '~' && url[1] == '/');
   }
}

.NET allows the syntax ~/ for URLs relative to the domain of your site - you can choose whether to support this in redirects.

Validate relative paths with:


function isRelative(url) {
  return url && url.match(/^\/[^\/\\]/);
}

Other Considerations

Check Client-Side Code Too!

Redirects can happen in client-side JavaScript, too! Validate any code that sets window.location, to ensure the URL is not taken from untrusted input.

Interstitial Pages

Some sites insert interstitial pages when the user is leaving the site – “you are now leaving tinyrobotninjas.com, you will be automatically redirected in 5 seconds”. This is a good defense against doppelganger domains – websites that have a very similar domain name, in order to trick the user into trusting them. If you implement an interstitial page that passes the URL in the query parameter, be sure to check the Referer header, or else they could be open to abuse.

Aggregator Sites

Aggregator sites often make use of redirects to do click-counting. URLs are chosen by the community, then when a user clicks on the link, the click-through count is incremented, and the user is redirected to their destination. If your site implements this type of functionality, redirects to external sites are part of doing business. Just be sure to check the Referer each time you do a redirect!

Further Reading