Skip to content

Commit 4d8b7f9

Browse files
authored
Improve short-url redirect validation (#84366)
1 parent 93b0273 commit 4d8b7f9

File tree

2 files changed

+32
-25
lines changed

2 files changed

+32
-25
lines changed

src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,39 @@
1919

2020
import { shortUrlAssertValid } from './short_url_assert_valid';
2121

22+
const PROTOCOL_ERROR = /^Short url targets cannot have a protocol/;
23+
const HOSTNAME_ERROR = /^Short url targets cannot have a hostname/;
24+
const PATH_ERROR = /^Short url target path must be in the format/;
25+
2226
describe('shortUrlAssertValid()', () => {
2327
const invalid = [
24-
['protocol', 'http://localhost:5601/app/kibana'],
25-
['protocol', 'https://localhost:5601/app/kibana'],
26-
['protocol', 'mailto:foo@bar.net'],
27-
['protocol', 'javascript:alert("hi")'], // eslint-disable-line no-script-url
28-
['hostname', 'localhost/app/kibana'],
29-
['hostname and port', 'local.host:5601/app/kibana'],
30-
['hostname and auth', 'user:pass@localhost.net/app/kibana'],
31-
['path traversal', '/app/../../not-kibana'],
32-
['deep path', '/app/kibana/foo'],
33-
['deep path', '/app/kibana/foo/bar'],
34-
['base path', '/base/app/kibana'],
28+
['protocol', 'http://localhost:5601/app/kibana', PROTOCOL_ERROR],
29+
['protocol', 'https://localhost:5601/app/kibana', PROTOCOL_ERROR],
30+
['protocol', 'mailto:foo@bar.net', PROTOCOL_ERROR],
31+
['protocol', 'javascript:alert("hi")', PROTOCOL_ERROR], // eslint-disable-line no-script-url
32+
['hostname', 'localhost/app/kibana', PATH_ERROR], // according to spec, this is not a valid URL -- you cannot specify a hostname without a protocol
33+
['hostname and port', 'local.host:5601/app/kibana', PROTOCOL_ERROR], // parser detects 'local.host' as the protocol
34+
['hostname and auth', 'user:pass@localhost.net/app/kibana', PROTOCOL_ERROR], // parser detects 'user' as the protocol
35+
['path traversal', '/app/../../not-kibana', PATH_ERROR], // fails because there are >2 path parts
36+
['path traversal', '/../not-kibana', PATH_ERROR], // fails because first path part is not 'app'
37+
['deep path', '/app/kibana/foo', PATH_ERROR], // fails because there are >2 path parts
38+
['deeper path', '/app/kibana/foo/bar', PATH_ERROR], // fails because there are >2 path parts
39+
['base path', '/base/app/kibana', PATH_ERROR], // fails because there are >2 path parts
40+
['path with an extra leading slash', '//foo/app/kibana', HOSTNAME_ERROR], // parser detects 'foo' as the hostname
41+
['path with an extra leading slash', '///app/kibana', HOSTNAME_ERROR], // parser detects '' as the hostname
42+
['path without app', '/foo/kibana', PATH_ERROR], // fails because first path part is not 'app'
43+
['path without appId', '/app/', PATH_ERROR], // fails because there is only one path part (leading and trailing slashes are trimmed)
3544
];
3645

37-
invalid.forEach(([desc, url]) => {
38-
it(`fails when url has ${desc}`, () => {
39-
try {
40-
shortUrlAssertValid(url);
41-
throw new Error(`expected assertion to throw`);
42-
} catch (err) {
43-
if (!err || !err.isBoom) {
44-
throw err;
45-
}
46-
}
46+
invalid.forEach(([desc, url, error]) => {
47+
it(`fails when url has ${desc as string}`, () => {
48+
expect(() => shortUrlAssertValid(url as string)).toThrowError(error);
4749
});
4850
});
4951

5052
const valid = [
5153
'/app/kibana',
54+
'/app/kibana/', // leading and trailing slashes are trimmed
5255
'/app/monitoring#angular/route',
5356
'/app/text#document-id',
5457
'/app/some?with=query',

src/plugins/share/server/routes/lib/short_url_assert_valid.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,22 @@ import { trim } from 'lodash';
2222
import Boom from '@hapi/boom';
2323

2424
export function shortUrlAssertValid(url: string) {
25-
const { protocol, hostname, pathname } = parse(url);
25+
const { protocol, hostname, pathname } = parse(
26+
url,
27+
false /* parseQueryString */,
28+
true /* slashesDenoteHost */
29+
);
2630

27-
if (protocol) {
31+
if (protocol !== null) {
2832
throw Boom.notAcceptable(`Short url targets cannot have a protocol, found "${protocol}"`);
2933
}
3034

31-
if (hostname) {
35+
if (hostname !== null) {
3236
throw Boom.notAcceptable(`Short url targets cannot have a hostname, found "${hostname}"`);
3337
}
3438

3539
const pathnameParts = trim(pathname === null ? undefined : pathname, '/').split('/');
36-
if (pathnameParts.length !== 2) {
40+
if (pathnameParts.length !== 2 || pathnameParts[0] !== 'app' || !pathnameParts[1]) {
3741
throw Boom.notAcceptable(
3842
`Short url target path must be in the format "/app/{{appId}}", found "${pathname}"`
3943
);

0 commit comments

Comments
 (0)