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:
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 hostredirectMapping
is an object that maps the pre-defined keys (e.g., what gets passed into theurl
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 thebaseHostname + redirectPath
variables together, avoiding any instance of user-supplied input making it's way directly into the Expressres.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:
Want a demo of Veracode Interactive Analysis?
Veracode Interactive Analysis (IAST) helps teams instantly discover vulnerabilities in their applications at runtime by embedding security into their development processes and integrating directly into their CI/CD pipelines. Get a demo.