Skip to content

Commit

Permalink
Merge pull request #628 from namecheap/fix/another-openredirect-case
Browse files Browse the repository at this point in the history
fix: another case of open redirect
  • Loading branch information
wRLSS authored Dec 10, 2024
2 parents 4f0ec19 + 63a2b50 commit 79a55f1
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 5 deletions.
8 changes: 7 additions & 1 deletion ilc/common/UrlProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ class UrlProcessor {
break;
}
}
return parsedUrl.toString().replace(this.#fakeBaseInCasesWhereUrlIsRelative, '');

let resultUrl = parsedUrl.toString().replace(this.#fakeBaseInCasesWhereUrlIsRelative, '');

// Strip any trailing double slashes from the pathname, if present
resultUrl = resultUrl.replace(/\/{2,}$/g, '/');

return resultUrl;
};
}

Expand Down
9 changes: 9 additions & 0 deletions ilc/common/UrlProcessor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@ describe('UrlProcessor', () => {
new UrlProcessor(UrlProcessor.routerHasTo.redirectToTrailingSlash).process(urlWithQueryAndHash),
).to.be.equal(urlWithQueryAndHashAndTrailingSlashAtTheEnd);
});

it('when the path starts with multiple slashes and ends with multiple slashes', () => {
const input = 'https://original.url//google.com//';
const expected = 'https://original.url/google.com/';

const urlProcessor = new UrlProcessor(UrlProcessor.routerHasTo.redirectToTrailingSlash);

chai.expect(urlProcessor.process(input)).to.be.equal(expected, `Failed for input: ${input}`);
});
});
});
});
12 changes: 12 additions & 0 deletions ilc/server/app.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,17 @@ describe('App', () => {

chai.expect(response.headers.location).to.be.eql('/someRoute/');
});

it('should reply with 302 in case of open redirect attempt but lead to correct route', async () => {
const response = await server.get('//google.com//').expect(302);

chai.expect(response.headers.location).to.be.eql('/google.com/');
});

it('should reply with 302 in case of open redirect attempt but lead to correct route for complex route', async () => {
const response = await server.get('//google.com//someRoute//').expect(302);

chai.expect(response.headers.location).to.be.eql('/google.com/someRoute/');
});
});
});
12 changes: 8 additions & 4 deletions ilc/server/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,20 @@ const onRequestFactory =
);

if (!req.raw.url.startsWith('/_ilc/')) {
const fixedUrl = IlcIntl.localizeUrl(i18nConfig, req.raw.url, { locale: pluginProcessedI18nConfig.locale });
const localizedUrl = IlcIntl.localizeUrl(i18nConfig, req.raw.url, {
locale: pluginProcessedI18nConfig.locale,
});

const containsOnlySlashes = (url) => /^\/+$/.test(url);
const shouldSkipRedirectForSlashes =
registryTrailingSlashSetting === UrlProcessor.routerHasTo.doNothing &&
containsOnlySlashes(fixedUrl) &&
containsOnlySlashes(localizedUrl) &&
containsOnlySlashes(req.raw.url);

if (fixedUrl !== req.raw.url && !shouldSkipRedirectForSlashes) {
return reply.redirect(fixedUrl);
if (localizedUrl !== req.raw.url && !shouldSkipRedirectForSlashes) {
const urlProcessor = new UrlProcessor(registryTrailingSlashSetting);
// Even if the localization triggered redirect we still need to ensure it is open-redirect safe
return reply.redirect(urlProcessor.process(localizedUrl));
}
}

Expand Down

0 comments on commit 79a55f1

Please sign in to comment.