Security context
LowGHSA-c59h-r6p8-q9wc CVE-2023-46298Published Oct 22, 2023

Next.js missing cache-control header may lead to CDN caching empty reply

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

0.9.9 → fixed in 13.4.20-canary.13

Details

Next.js before 13.4.20-canary.13 lacks a cache-control header and thus empty prefetch responses may sometimes be cached by a CDN, causing a denial of service to all users requesting the same URL via that CDN. Cloudflare considers these requests cacheable assets.

The fix

add cache control header

remorses· Aug 29, 2023, 06:22 PM+40f5cce3902e
packages/next/src/server/base-server.ts+4 0
@@ -1614,6 +1614,10 @@ export default abstract class Server<ServerOptions extends Options = Options> {
!(is404Page || pathname === '/_error')
) {
res.setHeader('x-middleware-skip', '1')
+ res.setHeader(
+ 'cache-control',
+ 'private, no-cache, no-store, max-age=0, must-revalidate'
+ )
res.body('{}').send()
return null
}

Add cache control header for prefetch empty responses

Tommaso De Rossi· Aug 29, 2023, 08:18 PM+4020d05958ff
packages/next/src/server/base-server.ts+4 0
@@ -1614,6 +1614,10 @@ export default abstract class Server<ServerOptions extends Options = Options> {
!(is404Page || pathname === '/_error')
) {
res.setHeader('x-middleware-skip', '1')
+ res.setHeader(
+ 'cache-control',
+ 'private, no-cache, no-store, max-age=0, must-revalidate'
+ )
res.body('{}').send()
return null
}

Export RedirectType from next/navigation (#54729)

Max Leiter· Aug 29, 2023, 06:05 PM+1104c3b7f9b7a
packages/next/src/client/components/navigation.ts+1 1
@@ -238,5 +238,5 @@ export function useSelectedLayoutSegment(
return selectedLayoutSegments[0]
}
-export { redirect, permanentRedirect } from './redirect'
+export { redirect, permanentRedirect, RedirectType } from './redirect'
export { notFound } from './not-found'
packages/next/src/client/components/redirect-boundary.tsx+0 1
@@ -26,7 +26,6 @@ function HandleRedirect({
const router = useRouter()
useEffect(() => {
- // @ts-ignore startTransition exists
React.startTransition(() => {
if (redirectType === RedirectType.push) {
router.push(redirect, {})
packages/third-parties/scripts/update-third-parties.js+0 3
@@ -23,10 +23,7 @@ function generateComponent(thirdParty) {
let props = ''
if (stylesheets?.length > 0) {
- // TODO: Remove ts-ignore after new <Script> component is published
- // New <Script> accepts stylesheet as a param. Otherwise it throws error
props += `
- // @ts-ignore
stylesheets={${JSON.stringify(stylesheets)}}`
}
packages/third-parties/src/google/index.tsx+0 1
@@ -30,7 +30,6 @@ export function YoutubeEmbed(args: Types.YoutubeEmbed) {
<Script
src={`https://cdn.jsdelivr.net/gh/paulirish/lite-youtube-embed@master/src/lite-yt-embed.js`}
strategy="lazyOnload"
- // @ts-ignore
stylesheets={[
'https://cdn.jsdelivr.net/gh/paulirish/lite-youtube-embed@master/src/lite-yt-embed.css',
]}
test/development/acceptance-app/fixtures/app-hmr-changes/app/(post)/components/tweet.tsx+0 1
@@ -22,7 +22,6 @@ export async function Tweet({ id, caption }: TweetArgs) {
return (
<div className="my-6">
<div className="flex justify-center">
- {/* @ts-ignore */}
<ReactTweet id={id} components={components} />
</div>
{caption && <Caption>{caption}</Caption>}
test/e2e/app-dir/app-prefetch/prefetching.test.ts+0 1
@@ -1,7 +1,6 @@
import { createNextDescribe } from 'e2e-utils'
import { check, waitFor } from 'next-test-utils'
-// @ts-ignore
import { NEXT_RSC_UNION_QUERY } from 'next/dist/client/components/app-router-headers'
const browserConfigWithFixedTime = {
test/e2e/app-dir/metadata-dynamic-routes/app/twitter-image.tsx+0 1
@@ -1,5 +1,4 @@
import { ImageResponse } from 'next/server'
-// @ts-ignore
import { ImageResponse as ImageResponse2 } from '@vercel/og'
// Node.js: Using @vercel/og external package, and should be aliased to "next/server" ImageResponse
test/e2e/app-dir/metadata/app/basic-edge/client.tsx+0 1
@@ -4,7 +4,6 @@ import ReactDOM from 'react-dom'
// NOTE: atm preload/prefetch/prefetchDNS are only supported in the SSR and client, not in RSC yet
export default function Client() {
- // @ts-ignore
ReactDOM.preload('/api/preload', { as: 'script' })
// @ts-ignore
ReactDOM.preconnect('/preconnect-url', { crossOrigin: 'use-credentials' })
More files changed — see the full commit.

References