Security context
High· 7.5GHSA-7gfc-8cq8-jh5f CVE-2024-51479CWE-285CWE-863Published Dec 17, 2024

Next.js authorization bypass vulnerability

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

9.5.5 → fixed in 14.2.15

Details

### Impact If a Next.js application is performing authorization in middleware based on pathname, it was possible for this authorization to be bypassed. ### Patches This issue was patched in Next.js `14.2.15` and later. If your Next.js application is hosted on Vercel, this vulnerability has been automatically mitigated, regardless of Next.js version. ### Workarounds There are no official workarounds for this vulnerability. #### Credits We'd like to thank [tyage](http://github.com/tyage) (GMO CyberSecurity by IERAE) for responsible disclosure of this issue.

The fix

backport: [i18n] Routing fix #70761 (#70976)

Zack Tanner· Oct 8, 2024, 07:29 PM+4401c8234eb20
packages/next/src/server/base-server.ts+8 0
@@ -908,6 +908,14 @@ export default abstract class Server<ServerOptions extends Options = Options> {
req.headers['x-forwarded-proto'] ??= isHttps ? 'https' : 'http'
req.headers['x-forwarded-for'] ??= originalRequest.socket?.remoteAddress
+ // Validate that if i18n isn't configured or the passed parameters are not
+ // valid it should be removed from the query.
+ if (!this.i18nProvider?.validateQuery(parsedUrl.query)) {
+ delete parsedUrl.query.__nextLocale
+ delete parsedUrl.query.__nextDefaultLocale
+ delete parsedUrl.query.__nextInferredLocaleFromDefault
+ }
+
// This should be done before any normalization of the pathname happens as
// it captures the initial URL.
this.attachRequestMeta(req, parsedUrl)
packages/next/src/server/future/helpers/i18n-provider.ts+31 0
@@ -134,6 +134,37 @@ export class I18NProvider {
}
}
+ /**
+ * Validates that the locale is valid.
+ *
+ * @param locale The locale to validate.
+ * @returns `true` if the locale is valid, `false` otherwise.
+ */
+ private validate(locale: string): boolean {
+ return this.lowerCaseLocales.includes(locale.toLowerCase())
+ }
+
+ /**
+ * Validates that the locales in the query object are valid.
+ *
+ * @param query The query object to validate.
+ * @returns `true` if the locale is valid, `false` otherwise.
+ */
+ public validateQuery(query: NextParsedUrlQuery) {
+ if (query.__nextLocale && !this.validate(query.__nextLocale)) {
+ return false
+ }
+
+ if (
+ query.__nextDefaultLocale &&
+ !this.validate(query.__nextDefaultLocale)
+ ) {
+ return false
+ }
+
+ return true
+ }
+
/**
* Analyzes the pathname for a locale and returns the pathname without it.
*
packages/next/src/server/lib/router-utils/resolve-routes.ts+5 0
@@ -214,6 +214,11 @@ export function getResolveRoutes(
parsedUrl.pathname = maybeAddTrailingSlash(parsedUrl.pathname)
}
}
+ } else {
+ // As i18n isn't configured we remove the locale related query params.
+ delete parsedUrl.query.__nextLocale
+ delete parsedUrl.query.__nextDefaultLocale
+ delete parsedUrl.query.__nextInferredLocaleFromDefault
}
const checkLocaleApi = (pathname: string) => {

References