What are Unsafe Redirects?

Unsafe or unvalidated redirects are important security considerations for any web developer. Express provides native support for redirects, making them easy to implement and use. However, Express leaves the work of performing input validation to the developer.

Here's the definition according to OWASP.org's "Unvalidated Redirects and Forwards" cheat sheet:

Unvalidated redirects and forwards are possible when a web application accepts untrusted input that could cause the web application to redirect the request to a URL contained within untrusted input.

Redirects are commonly used in login and authentication processes, so users can be redirected back to the page they were on before logging in. Other scenarios exist, but vary based on business need or application type.

Why are they bad?

Redirects that do not validate user input can enable attackers to launch phishing scams, steal user credentials, and perform other malicious actions.

Important: When redirects are implemented in Node.js and/or Express, it's important to perform input validation on the server-side.

If an attacker discovers that you are not validating external, user-supplied input, they may exploit this vulnerability by posting specially-crafted links on forums, social media, and other public places to get users to click it.

At face value, these URLs may look legitimate to the user - since all of them will contain your organization's hostname:

https://example.com/login?url=http://examp1e.com/bad/things

However, if the server-side redirect logic does not validate data entering the url parameter, your users may end up on a site that looks exactly like yours (examp1e.com), but ultimately serves the needs of criminal hackers!

This is just one example of how attackers can take advantage of unsafe redirect logic.

An Example of an Unsafe Redirect

In the following code, you'll see that /login accepts unvalidated data from the url parameter and passes it directly into the Express res.redirect() method. As a result, Express will redirect the user to whatever URL is entered or supplied so long as the user is authenticated.

var express = require('express');
var port = process.env.PORT || 3000;
var app = express();

app.get('/login', function (req, res, next) {

	if(req.session.isAuthenticated()) {

		res.redirect(req.query.url);
	}
}); 

app.get('/account', function (req, res, next) {
    res.send('Account page');
});

app.get('/profile', function (req, res, next) {
    res.send('Profile page');
});

app.listen(port, function() {
    console.log('Server listening on port ' + port);
});

Input Validation helps you prevent Unsafe Redirects

In general, it's best to avoid use of redirects and forwards in your code.

If you absolutely need to use a redirect in your code, the most preferred method is using pre-defined keys that map to a specific destination. This is known as the whitelist method. Here's a way to implement this:

  • baseHostname ensures any redirect keeps the user on our host
  • redirectMapping is an object that maps the pre-defined keys (e.g., what gets passed into the url paramer) to specific paths on the server
  • The validateRedirect() method asserts whether or not the pre-defined keys exists. If they exist, it returns the appropriate path to redirect to.
  • We modified our /login logic to concatenate the baseHostname + redirectPath variables together, avoiding any instance of user-supplied input making it's way directly into the Express res.redirect() method.
  • Finally, we use the encodeURI() method as extra assurance that the URI portion of the concatenaed string is encoded correctly – allowing for a clean redirect.
//Configure your whitelist
var baseHostname = "https://example.com";
var redirectMapping = {
    'account': '/account',
    'profile': '/profile'
}

//Create a function to validate whitelist
function validateRedirect(key) {
    if(key in redirectMapping) {

        return redirectMapping[key];
    }else{

        return false;
    }
}

app.get('/login', function (req, res, next) {
	if(req.session.isAuthenticated()) {
		redirectPath = validateRedirect(req.query.url);

		if(redirectPath) {
			res.redirect(encodeURI(baseHostname + redirectPath));
		}else{
			res.send('Not a valid redirect!');
		}
	}
}); 

Other scenarios

There may be scenarios where it's impractical to whitelist every single combination, but you still want to redirect the user and keep them on your domain within certain boundaries. It's best to do this when the externally-supplied value  follows a specific pattern, such as a 16 character alphanumeric string. Alphanumeric strings are ideal since they do not contain any special characters that may introduce other attacks such as Directory/Path Traversal (which relies on characters such as .. and backward/forward slashes).

For example, you may want to redirect the user back to a specific item on your e-commerce site after they login. Since the e-commerce site has a unique, alphanumeric value for each product, you can implement a safe redirect by always validating the external input against a RegEx whitelist. In this case, that's the productId variable. See below:

//Configure your whitelist
var baseHostname = "https://example.com";

app.get('/login', function (req, res, next) {
    productId = (req.query.productId || '');
    whitelistRegEx = /^[a-zA-Z0-9]{16}$/;

    if(productId) {
        
        //Validate the productId is alphanumeric and exactly 16 characters
        if(whitelistRegEx.test(productId)) {

            res.redirect(encodeURI(baseHostname + '/item/' + productId));
        }else{

            //The productId did not meet the RegEx whitelist, so return an error
            res.send('Invalid product ID');
        }
    }else{
    
        //No productId was provided, so redirect to home page
        res.redirect('/');
    }
}); 

Finally, it's important to warn the user that they are being automatically redirected. In cases where you're intentionally redirecting the user outside of your domain, you may want to create an intermediate page in the process that gives a warning like this and includes the URL you are redirecting to:

It's the polite thing to do.

Want a demo of Hailstone?

Hailstone is a continuous application security platform that uses your existing functional tests to find vulnerabilities in your code. We're currently in early access and seeking Design Partners to provide us with feedback.

If you're interested in joining, or seeing a demo, sign up for our mailing list - we'll be in touch shortly!

You can find us on Twitter at @HailstoneSec