Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is preventing open redirects attack in nodejs secure?

I'm trying to prevent open redirect attack. Please look at the code below and check for security:

var = require('url');

// http://example.com/login?redirect=http://example.com/dashboard
app.route('/login', function (req, res, next) {
   var redirect = req.query.redirect,
        paths = url.parse(redirect); 

   if (paths.host !== req.headers.host) {
      return next(new Error('Open redirect attack detected'));
   }

   return res.redirect(redirect);
});

Is it enough for preventing open redirect attack or should I add anything else?

like image 898
Erik Avatar asked Jun 06 '16 23:06

Erik


2 Answers

CWE-601: URL Redirection to Untrusted Site ('Open Redirect')

Description of Open Redirect:

An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance.

The suggestion of input validation strategy to prevent open redirect attack:

Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a whitelist of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. Do not rely exclusively on looking for malicious or malformed inputs (i.e., do not rely on a blacklist). A blacklist is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, blacklists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright. Use a whitelist of approved URLs or domains to be used for redirection.

Use req.headers.host, req.host or req.hostname is insecure, because req.headers can be forged (eg. a HTTP request have a custom Host header to access a express app written in code below)

var url = require('url');

app.get('/login', function (req, res, next) {
    var redirect = req.query.redirect,
        targetUrl = url.parse(redirect);
    console.log('req.headers.host: [%s]', req.headers.host);
    console.log('req.host: [%s]', req.host);
    console.log('req.hostname: [%s]', req.hostname);
    if (targetUrl.host != req.headers.host) {
        return next(new Error('Open redirect attack detected'));
    }
    return res.redirect(redirect);
});

Use curl to make a request:

$ curl -H 'Host: malicious.example.com' 'http://localhost:3012/login?redirect=http://malicious.example.com' -i
HTTP/1.1 302 Found
X-Powered-By: Express
Location: http://malicious.example.com
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 54
Date: Mon, 13 Jun 2016 06:30:55 GMT
Connection: keep-alive

$ #server output
req.headers.host: [malicious.example.com]
req.host: [malicious.example.com]
req.hostname: [malicious.example.com]

I suggest you use whitelist to validate input, a example code below:

const WHITELIST_TO_REDIRECT = new Set(["localhost:3012", "www.realdomain.com"]);

app.get('/login', function (req, res, next) {
   var redirect = req.query.redirect,
        targetUrl = url.parse(redirect);
   console.log("req.hostname: [%s]", req.hostname);
   console.log("url.host: [%s]", targetUrl.host);
   if (!WHITELIST_TO_REDIRECT.has(targetUrl.host)) {
      return next(new Error('Open redirect attack detected'));
   }

   return res.redirect(redirect);
});
like image 156
Shawyeok Avatar answered Oct 03 '22 10:10

Shawyeok


In this situation I'd use a HMAC. This will allow the login controller to verify that the redirect parameter was generated by someone that knows the secret key.

When you generate the "login" url you add a HMAC digest of the redirect parameter to the url along with the redirect parameter itself.

The login handler can use the HMAC to ensure that the redirect parameter was generated by a trusted server that knows the HMAC secret key thereby preventing open redirect attacks.

e.g.

var crypto = require('crypto');
var secretKey = 'change-me';
var loginUrl = 'http://example.com/login'

// called to work out where to redirect to for login
function getLoginUrl(redirectBackUrl) {
    var sig = crypto.createHmac('sha1', secretKey)
                    .update(redirectBackUrl)
                    .digest('hex');

    return loginUrl 
         + '?redirect=' 
         + encodeURIComponent(redirectBackUrl) 
         +'&sig=' + sig;
}

// called by the login function to ensure that the 
// redirect parameter is valid
function isRedirectUrlValid(url, sig) {
    var expectedSig
            = crypto.createHmac('sha1', secretKey)
                    .update(url)
                    .digest('hex');

     return expectedSig === sig;
}
like image 44
Andrew Skirrow Avatar answered Oct 03 '22 11:10

Andrew Skirrow