From 51111705a3a686af0331046ef745d381e515b604 Mon Sep 17 00:00:00 2001 From: zhongliang02 Date: Wed, 6 Nov 2024 15:05:41 +0800 Subject: [PATCH] fix dynamic routes bypass (#24) --- packages/validators/src/__tests__/url.test.ts | 12 +++++++++++ packages/validators/src/url/utils.ts | 20 +++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/validators/src/__tests__/url.test.ts b/packages/validators/src/__tests__/url.test.ts index b11ef3f..87bb612 100644 --- a/packages/validators/src/__tests__/url.test.ts +++ b/packages/validators/src/__tests__/url.test.ts @@ -143,6 +143,18 @@ describe('UrlValidator with base URL', () => { it('should not allow Next.js dynamic routes', () => { expect(() => validator.parse('/[[x]]javascript:alert(1337)/[y]/[z]?x&y&z')).toThrow(UrlValidationError) }) + + it('should not allow Next.js dynamic routes', () => { + expect(() => + validator.parse('/[[x]x]]https://[y]y]//example.com/[[/[[x]x]]/y?x=[[x]&x]x&%2F[[x=[[/[[x]&y=[y]&y]y='), + ).toThrow(UrlValidationError) + }) + + it('should not allow Next.js dynamic routes', () => { + expect(() => validator.parse('/[[x]x]]javascript:alert(1)%2F%2F/[[/[[x]x]]/y?x=[[x]&x]x&%2F[[x=[[/[[x]')).toThrow( + UrlValidationError, + ) + }) }) describe('UrlValidator with invalid options', () => { diff --git a/packages/validators/src/url/utils.ts b/packages/validators/src/url/utils.ts index 4541eac..73a141c 100644 --- a/packages/validators/src/url/utils.ts +++ b/packages/validators/src/url/utils.ts @@ -1,7 +1,8 @@ import { UrlValidationError } from '@/url/errors' import { UrlValidatorWhitelist } from '@/url/options' -const DYNAMIC_ROUTE_SEGMENT_REGEX = /\[\[?([^\]]+)\]?\]/g +// regex from https://github.com/vercel/next.js/blob/8cb8edb686ec8ddf7e24c69545d11175fcb9df02/packages/next/src/shared/lib/router/utils/is-dynamic.ts#L7 +const DYNAMIC_ROUTE_SEGMENT_REGEX = /\/\[[^/]+?\](?=\/|$)/ const IS_NOT_HOSTNAME_REGEX = /[^.]+\.[^.]+/g export const resolveRelativeUrl = (url: string, baseOrigin?: URL): URL => { @@ -26,19 +27,8 @@ export const resolveRelativeUrl = (url: string, baseOrigin?: URL): URL => { return normalizedUrl } -/* As of Next.js 14.2.5, router.push() resolves dynamic routes using query parameters. */ -const resolveNextDynamicRoute = (url: URL): URL => { - const pathname = url.pathname - const query = new URLSearchParams(url.search) - const resolvedPathname = pathname.replace(DYNAMIC_ROUTE_SEGMENT_REGEX, (_, name: string) => { - const value = query.get(name) || '' - query.delete(name) - return value - }) - - const result = new URL(url.href) - result.pathname = resolvedPathname - return result +export const isDynamicRoute = (url: URL): boolean => { + return DYNAMIC_ROUTE_SEGMENT_REGEX.test(url.pathname) } export const isSafeUrl = (url: URL, whitelist: UrlValidatorWhitelist) => { @@ -59,7 +49,7 @@ export const isSafeUrl = (url: URL, whitelist: UrlValidatorWhitelist) => { } // don't allow dynamic routes - if (resolveNextDynamicRoute(url).href !== url.href) { + if (isDynamicRoute(url)) { return false } return true