fix: open redirection vulnerability

This commit is contained in:
Julian Lam 2025-09-18 13:31:14 -04:00
parent 75f297a041
commit 1b32abbf11
3 changed files with 20 additions and 7 deletions

View file

@ -16,6 +16,13 @@ const user = require.main.require('./src/user');
const meta = require.main.require('./src/meta');
const helpers = require.main.require('./src/controllers/helpers');

const guard = (path) => {
let url = new URL(path, nconf.get('url'));
url = url.hostname === nconf.get('url_parsed').hostname ? url : nconf.get('url');

return url.toString();
};

const Controllers = module.exports;


@ -70,7 +77,7 @@ Controllers.renderTotpChallenge = async (req, res, next) => {
const single = parseInt(req.query.single, 10) === 1;

if (req.session.tfa === true && !req.session.tfaForce) {
return res.redirect(nconf.get('relative_path') + (req.query.next || '/'));
return res.redirect(guard(nconf.get('relative_path') + (req.query.next || '/')));
}

const error = req.flash('error');
@ -97,7 +104,7 @@ Controllers.renderAuthnChallenge = async (req, res, next) => {
const single = parseInt(req.query.single, 10) === 1;

if (req.session.tfa === true && ((req.query.next && !req.query.next.startsWith('/admin')) || !req.session.tfaForce)) {
return res.redirect(nconf.get('relative_path') + (req.query.next || '/'));
return res.redirect(guard(nconf.get('relative_path') + (req.query.next || '/')));
}

if (!await parent.hasAuthn(uid)) {
@ -137,7 +144,7 @@ Controllers.renderBackup = async (req, res, next) => {
const single = parseInt(req.query.single, 10) === 1;

if (req.session.tfa === true && ((req.query.next && !req.query.next.startsWith('/admin')) || !req.session.tfaForce)) {
return res.redirect(nconf.get('relative_path') + (req.query.next || '/'));
return res.redirect(guard(nconf.get('relative_path') + (req.query.next || '/')));
}

const error = req.flash('error');

View file

@ -21,6 +21,12 @@ const controllerHelpers = require.main.require('./src/controllers/helpers');
const SocketPlugins = require.main.require('./src/socket.io/plugins');

const atob = base64str => Buffer.from(base64str, 'base64').toString('binary');
const guard = (path) => {
let url = new URL(path, nconf.get('url'));
url = url.hostname === nconf.get('url_parsed').hostname ? url : nconf.get('url');

return url.toString();
};

const plugin = {
_f2l: undefined,
@ -57,7 +63,7 @@ plugin.init = async (params) => {
delete req.session.tfaForce;
req.session.meta.datetime = Date.now();
user.auth.addSession(req.uid, req.sessionID, req.session.meta.uuid);
res.redirect(nconf.get('relative_path') + (req.query.next || '/'));
res.redirect(guard(nconf.get('relative_path') + (req.query.next || '/')));
});
hostHelpers.setupPageRoute(router, '/login/2fa/authn', [hostMiddleware.ensureLoggedIn], controllers.renderAuthnChallenge);

@ -65,7 +71,7 @@ plugin.init = async (params) => {
hostHelpers.setupPageRoute(router, '/login/2fa/backup', [hostMiddleware.ensureLoggedIn], controllers.renderBackup);
router.post('/login/2fa/backup', hostMiddleware.ensureLoggedIn, controllers.processBackup, (req, res) => {
req.session.tfa = true;
res.redirect(nconf.get('relative_path') + (req.query.next || '/'));
res.redirect(guard(nconf.get('relative_path') + (req.query.next || '/')));
});
router.put('/login/2fa/backup', hostMiddleware.requireUser, middlewares.requireSecondFactor, hostMiddleware.applyCSRF, controllers.generateBackupCodes);

@ -167,7 +173,7 @@ plugin.addRoutes = async ({ router, middleware, helpers }) => {
req.session.meta.datetime = Date.now();

helpers.formatApiResponse(200, res, {
next: req.query.next || '/',
next: guard(req.query.next || '/'),
});
});


View file

@ -22,7 +22,7 @@ define('forum/login-authn', ['api', 'alerts', 'hooks'], function (api, alerts, h
iconEl.classList.remove('fa-spin');
iconEl.classList.add('fa-check');
iconEl.classList.add('text-success');
document.location = config.relative_path + next;
document.location = next;
}).catch((err) => {
alerts.error(err);
ajaxify.refresh();