Skip to content

Commit d12b53c

Browse files
yasnagatpierre-lehnen-rcjulio-rocketchat
authored
fix: add validation for SAML SLO redirect URLs (RocketChat#38994)
Co-authored-by: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Co-authored-by: Julio Araujo <julio.araujo@rocket.chat>
1 parent 610cc03 commit d12b53c

File tree

2 files changed

+50
-5
lines changed

2 files changed

+50
-5
lines changed

.changeset/metal-rice-retire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/meteor': patch
3+
---
4+
5+
Adds SAML redirect validation by matching request parameters and configured IdP SLO

apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class SAML {
7474
case 'logout':
7575
return this.processLogoutAction(req, res, service);
7676
case 'sloRedirect':
77-
return this.processSLORedirectAction(req, res);
77+
return this.processSLORedirectAction(req, res, service);
7878
case 'authorize':
7979
return this.processAuthorizeAction(res, service, samlObject);
8080
case 'validate':
@@ -384,18 +384,58 @@ export class SAML {
384384
await logOutUser(inResponseTo);
385385
} finally {
386386
res.writeHead(302, {
387-
Location: req.query.RelayState,
387+
Location: Meteor.absoluteUrl(),
388388
});
389389
res.end();
390390
}
391391
});
392392
}
393393

394-
private static processSLORedirectAction(req: IIncomingMessage, res: ServerResponse): void {
394+
private static processSLORedirectAction(req: IIncomingMessage, res: ServerResponse, service: IServiceProviderOptions): void {
395+
const { idpSLORedirectURL } = service;
396+
const userRedirect = req.query.redirect as string;
397+
398+
if (!idpSLORedirectURL) {
399+
res.writeHead(500);
400+
res.end('SLO redirect not configured');
401+
return;
402+
}
403+
404+
if (!userRedirect || typeof userRedirect !== 'string') {
405+
res.writeHead(400);
406+
res.end('Missing redirect parameter');
407+
return;
408+
}
409+
410+
let configuredURL: URL;
411+
let requestURL: URL;
412+
413+
try {
414+
configuredURL = new URL(idpSLORedirectURL);
415+
requestURL = new URL(userRedirect);
416+
} catch {
417+
res.writeHead(400);
418+
res.end('Invalid URL format');
419+
return;
420+
}
421+
422+
if (configuredURL.origin !== requestURL.origin) {
423+
res.writeHead(403);
424+
res.end('Unauthorized redirect origin');
425+
return;
426+
}
427+
428+
const normalizePath = (p: string): string => p.replace(/\/+$/, '') || '/';
429+
if (normalizePath(configuredURL.pathname) !== normalizePath(requestURL.pathname)) {
430+
res.writeHead(403);
431+
res.end('Unauthorized redirect path');
432+
return;
433+
}
434+
395435
res.writeHead(302, {
396-
// credentialToken here is the SAML LogOut Request that we'll send back to IDP
397-
Location: req.query.redirect,
436+
Location: requestURL.toString(),
398437
});
438+
399439
res.end();
400440
}
401441

0 commit comments

Comments
 (0)