From 27b912b9b0e5a0be716a4d51cd4c9097d5da14f8 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Fri, 10 Feb 2023 20:01:57 +0100 Subject: [PATCH] security: check schema for URL previews Changelog: Fixed --- .../backend/src/server/web/url-preview.ts | 22 ++++++++++++------- packages/client/src/components/global/url.vue | 1 + .../client/src/components/url-preview.vue | 5 ++++- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts index 7ff381443..259912e55 100644 --- a/packages/backend/src/server/web/url-preview.ts +++ b/packages/backend/src/server/web/url-preview.ts @@ -38,6 +38,14 @@ export const urlPreviewHandler = async (ctx: Koa.Context): Promise => { logger.succ(`Got preview of ${url}: ${summary.title}`); + if (summary.url && !(summary.url.startsWith('http://') || summary.url.startsWith('https://'))) { + throw new Error('unsupported schema included'); + } + + if (summary.player?.url && !(summary.player.url.startsWith('http://') || summary.player.url.startsWith('https://'))) { + throw new Error('unsupported schema included'); + } + summary.icon = wrap(summary.icon); summary.thumbnail = wrap(summary.thumbnail); @@ -54,12 +62,10 @@ export const urlPreviewHandler = async (ctx: Koa.Context): Promise => { }; function wrap(url?: string): string | null { - return url != null - ? url.match(/^https?:\/\//) - ? `${config.url}/proxy/preview.webp?${query({ - url, - preview: '1', - })}` - : url - : null; + if (url == null) return null; + if (!['http:', 'https:'].includes(new URL(url).protocol)) return null; + return config.url + '/proxy/preview.webp?' + query({ + url, + preview: '1', + }); } diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue index babf49887..47811ae00 100644 --- a/packages/client/src/components/global/url.vue +++ b/packages/client/src/components/global/url.vue @@ -35,6 +35,7 @@ const props = withDefaults(defineProps<{ const self = props.url.startsWith(local); const uri = new URL(props.url); +if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url'); let el: HTMLElement | null = $ref(null); let schema = $ref(uri.protocol); diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue index d866fd9ea..0add58761 100644 --- a/packages/client/src/components/url-preview.vue +++ b/packages/client/src/components/url-preview.vue @@ -54,6 +54,7 @@ let player = $ref({ let playerEnabled = $ref(false); const requestUrl = new URL(props.url); +if(!['http:', 'https:].includes(requestUrl.protocol)) throw new Error('invalid url'); if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) { requestUrl.hostname = 'www.youtube.com'; @@ -72,7 +73,9 @@ fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).the icon = info.icon; sitename = info.sitename; fetching = false; - player = info.player; + if (['http:', 'https:'].includes(new URL(info.player.url).protocol)) { + player = info.player; + } }); });