diff --git a/src/core/public/http/external_url_service.test.ts b/src/core/public/http/external_url_service.test.ts index dcd99280151bf..ee757c5046760 100644 --- a/src/core/public/http/external_url_service.test.ts +++ b/src/core/public/http/external_url_service.test.ts @@ -155,6 +155,40 @@ describe('External Url Service', () => { }); }); + internalRequestScenarios.forEach(({ description, policy, allowExternal }) => { + describe(description, () => { + it('allows relative URLs without absolute path replacing last path segment', () => { + const { setup } = setupService({ location, serverBasePath, policy }); + const urlCandidate = `my_other_app?foo=bar`; + const result = setup.validateUrl(urlCandidate); + + expect(result).toBeInstanceOf(URL); + expect(result?.toString()).toEqual(`${kibanaRoot}/app/${urlCandidate}`); + }); + + it('allows relative URLs without absolute path replacing multiple path segments', () => { + const { setup } = setupService({ location, serverBasePath, policy }); + const urlCandidate = `/api/my_other_app?foo=bar`; + const result = setup.validateUrl(`..${urlCandidate}`); + + expect(result).toBeInstanceOf(URL); + expect(result?.toString()).toEqual(`${kibanaRoot}${urlCandidate}`); + }); + + if (!allowExternal) { + describe('handles bypass of base path via relative URL', () => { + it('does not allow relative URLs that escape base path', () => { + const { setup } = setupService({ location, serverBasePath, policy: [] }); + const urlCandidate = `../../base_path_escape`; + const result = setup.validateUrl(urlCandidate); + + expect(result).toBeNull(); + }); + }); + } + }); + }); + describe('handles protocol resolution bypass', () => { it('does not allow relative URLs that include a host', () => { const { setup } = setupService({ location, serverBasePath, policy: [] }); @@ -194,7 +228,7 @@ describe('External Url Service', () => { internalRequestScenarios.forEach(({ description, policy }) => { describe(description, () => { - it('allows relative URLs', () => { + it('allows relative URLs with absolute path', () => { const { setup } = setupService({ location, serverBasePath, policy }); const urlCandidate = `/some/path?foo=bar`; const result = setup.validateUrl(`${serverBasePath}${urlCandidate}`); @@ -214,6 +248,28 @@ describe('External Url Service', () => { }); }); + internalRequestScenarios.forEach(({ description, policy }) => { + describe(description, () => { + it('allows relative URLs without absolute path replacing last path segment', () => { + const { setup } = setupService({ location, serverBasePath, policy }); + const urlCandidate = `my_other_app?foo=bar`; + const result = setup.validateUrl(urlCandidate); + + expect(result).toBeInstanceOf(URL); + expect(result?.toString()).toEqual(`${kibanaRoot}/app/${urlCandidate}`); + }); + + it('allows relative URLs without absolute path replacing multiple path segments', () => { + const { setup } = setupService({ location, serverBasePath, policy }); + const urlCandidate = `/api/my_other_app?foo=bar`; + const result = setup.validateUrl(`..${urlCandidate}`); + + expect(result).toBeInstanceOf(URL); + expect(result?.toString()).toEqual(`${kibanaRoot}${urlCandidate}`); + }); + }); + }); + describe('handles protocol resolution bypass', () => { it('does not allow relative URLs that include a host', () => { const { setup } = setupService({ location, serverBasePath, policy: [] }); diff --git a/src/core/public/http/external_url_service.ts b/src/core/public/http/external_url_service.ts index dbd7313b65059..166e167b3b994 100644 --- a/src/core/public/http/external_url_service.ts +++ b/src/core/public/http/external_url_service.ts @@ -14,7 +14,7 @@ import { InjectedMetadataSetup } from '../injected_metadata'; import { Sha256 } from '../utils'; interface SetupDeps { - location: Pick; + location: Pick; injectedMetadata: InjectedMetadataSetup; } @@ -52,11 +52,11 @@ function normalizeProtocol(protocol: string) { const createExternalUrlValidation = ( rules: IExternalUrlPolicy[], - location: Pick, + location: Pick, serverBasePath: string ) => { - const base = new URL(location.origin + serverBasePath); return function validateExternalUrl(next: string) { + const base = new URL(location.href); const url = new URL(next, base); const isInternalURL =