防止在nodejs中的开放重定向攻击安全吗?

问题描述:

我正试图防止打开重定向攻击。请看下面的代码并检查安全性:防止在nodejs中的开放重定向攻击安全吗?

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); 
}); 

防止打开重定向攻击还是应该添加其他东西?

+1

可能是更容易使用'重定向= dashboard',然后在服务器使用'res.redirect( 'http://example.com/' + req.query.redirect);'这种方式重定向将永远不要离开你的服务器。 – Molda

+0

谢谢。如何不在'res.redirect'中硬编码http:// example.com /',但使用'req.host'或类似的东西? – Erik

+0

'res.redirect('http://'+ req.host +'/'+ req.query.redirect);' – Molda

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

Open Redirect描述:

一个HTTP参数可能包含一个URL值,并可能导致该web应用程序将请求重定向到指定的URL。通过将URL值修改为恶意网站,攻击者可能会成功启动网络钓鱼骗局并窃取用户凭据。由于修改后的链接中的服务器名称与原始网站相同,因此网络钓鱼尝试的外观更加值得信赖。

input validation战略的建议,以避免开放重定向攻击:

假设所有的输入是恶意的。使用“接受已知良好”输入验证策略,即使用严格符合规范的可接受输入的白名单。拒绝任何不严格符合规范的输入,或者将其转换为某种情况。 不要完全依赖寻找恶意或畸形输入(即不要依赖黑名单)。黑名单可能会遗漏至少一个不良的输入,特别是如果代码的环境发生变化。这可能给攻击者足够的空间来绕过预期的验证。然而,黑名单对于检测潜在的攻击或确定哪些输入是如此恶劣以致应该被彻底拒绝是有用的。 使用批准的URL或域的白名单用于重定向。

使用req.headers.hostreq.hostreq.hostname是不安全的,因为req.headers可以锻造(例如,HTTP请求具有一个自定义Host头访问写在下面的代码明示应用)

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); 
}); 

使用curl发出请求:

$ 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] 

我建议你使用白名单来验证输入,一个示例代码如下:

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); 
}); 
+0

感谢您的答案。是否有可能不使用白名单,但通过正则表达式进行验证? – Erik

+0

当然。当白名单不太长时,正则表达式也代表了一系列'白名单',硬编码白名单。 – Shawyeok

+0

如果主机头由用户的浏览器伪造,该用户有更大的安全问题。虽然这是明智的做法,但从安全的角度来看,我无法看到与“主机”标题相比有什么优惠。 – Brendon

在这种情况下,我会使用HMAC。这将允许登录控制器验证redirect参数是由知道密钥的人员生成的。

当您生成“登录”url时,您将redirect参数的HMAC摘要与重定向参数本身一起添加到url中。

登录处理程序可以使用HMAC来确保redirect参数由知道HMAC密钥的受信任服务器生成,从而防止开放重定向攻击。

例如

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; 
}