本文详解Node.js及Express框架中未经验证重定向的安全风险,分析攻击者如何利用该漏洞进行网络钓鱼。通过白名单映射和正则表达式验证两种方案,结合encodeURI编码,提供具体的代码实现步骤,帮助开发者构建安全的重定向逻辑,保护用户凭证与数据安全。
Node.js中防御不安全重定向的最佳实践
导语:
在早期的浏览器竞争阶段,Google预判现代Web应用需要强大的JavaScript引擎,因此自主研发了高性能开源的V8引擎。2009年,Ryan Dahl基于JavaScript语言与V8引擎推出了开源Web服务器项目,命名为Node.js。
对于Web开发人员而言,未经验证或不安全的重定向是必须重视的安全议题。Express框架内置了重定向支持,使其实现简便。Express是一个轻量级且灵活的Node.js Web应用框架,为Web及移动应用提供了丰富的功能集。
什么是不安全的重定向?
什么是不安全的重定向?
未经验证的重定向是Web开发中的关键安全考量。虽然Express提供了便捷的重定向功能,但它将输入验证的责任完全交由开发者。Express作为一个轻量级框架,为应用提供了强大功能,但不会自动处理安全性验证。
以下是OWASP.org对“未经验证的重定向和转发”的定义:
若Web应用程序接受不可信输入,并据此将请求重定向至包含该不可信输入的URL,则构成了未经验证的重定向与转发漏洞。
重定向常用于登录和身份验证流程,以便在用户登录前将其引导至原页面。但根据业务需求或应用类型不同,也存在其他重定向场景。
为什么要避免重定向?
为什么要避免重定向?
若不对用户输入进行验证,攻击者可利用此漏洞发起网络钓鱼攻击,窃取用户凭据并执行其他恶意操作。
注意:在Node.js或Express中实现重定向时,必须在服务器端执行严格的输入验证。
若攻击者发现系统未验证外部提供的输入,他们可能在论坛、社交媒体等公共场所发布精心设计的链接,诱导用户点击。
这些URL表面看起来合法且无害,因为它们通常包含目标主机名:
https://example.com/login?url=http://examp1e.com/bad/things
然而,若服务器端的重定向逻辑未对url参数进行验证,用户最终可能被重定向至黑客预设的网站(如examp1e.com),从而满足攻击者的目的。以上仅为不安全重定向逻辑被利用的一个示例。
以下示例展示了将用户输入直接传递给Express的res.redirect()方法。因此,只要用户通过身份验证,Express便会将用户重定向至输入提供的URL。
不安全重定向示例
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);
});
输入验证有助于防止不安全的重定向
输入验证有助于防止不安全的重定向
通常建议避免在代码中使用重定向和转发。若必须使用,首选方法是使用映射到特定目标的预定义输入,即白名单机制。以下是实现该方法的具体步骤:
1. baseHostname确保任何重定向都将用户保留在研究人员的主机域内;
2. redirectMapping是一个对象,将预定义输入(如传递给url paramer的内容)映射到服务器上的特定路径;
3. validateRedirect()方法判断预定义输入是否存在,若存在则返回相应的重定向路径;
4. 研究人员修改了/login逻辑,将baseHostname与redirectPath变量拼接,避免了任何用户提供的输入直接传递给Express res.redirect()方法;
5. 最后,研究人员使用encodeURI()方法作为额外的安全保证,确保拼接后的URI部分被正确编码,从而实现干净的重定向。
白名单机制实现
//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!');
}
}
});
其他重定向场景
其他重定向场景
在某些情况下,将所有组合列入白名单可能不切实际,但安全平台仍希望将用户重定向并限制在域内的某些边界中。当外部提供的值遵循特定模式(例如16个字符的字母数字字符串)时,这种做法较为合适。字母数字字符串是理想选择,因为它们不包含可能引入其他攻击的特殊字符(如目录/路径遍历所需的…和斜杠等字符)。
例如,安全平台可能希望在用户登录后将其重定向回电子商务网站上的特定产品。由于电子商务网站对每种产品都有唯一的字母数字值,安全平台可通过始终根据RegEx白名单验证外部输入来实现安全重定向。在本文示例中,研究者使用的是productId变量。
正则表达式白名单验证
//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('/');
}
});
最后,安全平台发出警告,提醒用户他们正在被自动重定向。如果安全平台有意将用户重定向至域外,则可能在流程中创建一个中间页面,发出如下警告,并包含用户要重定向到的URL。

注:本文以Hailstone为例进行讲解,Hailstone是一个具有查找代码中漏洞功能的应用程序安全平台。
本文翻译自:https://blog.hailstone.io/how-to-prevent-unsafe-redirects-in-node-js
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对本站的支持。