Next.js is a popular React framework for building full-stack, fast web applications. In May 2024, a serious security flaw was discovered in Next.js, tracked as CVE-2024-46982. This post explains what happened, how the exploit works, who is at risk, and what you should do to stay secure. We'll keep it simple and easy to follow, with plenty of examples.

What is CVE-2024-46982?

CVE-2024-46982 is a cache poisoning vulnerability affecting certain versions of Next.js. By sending a specially crafted HTTP request, an attacker can force Next.js to cache content for routes that should not be cached—potentially exposing private or sensitive data to other users.

This issue only impacts non-dynamic server-side rendered (SSR) routes when using the pages router (not the app router).

You use the pages directory ("pages router") instead of the "app router"

3. You have non-dynamic SSR routes (for example, pages/dashboard.tsx, not pages/blog/[slug].tsx)

> Example of a Vulnerable SSR Page:
> - /pages/account.js or /pages/dashboard.tsx
>   (These always serve the same file, but the content is generated server-side per user/session.)

What Happens in This Exploit?

When an attacker sends a special request, Next.js mistakenly caches content that should never be cached and includes a Cache-Control header:

Cache-Control: s-maxage=1, stale-while-revalidate

If your application is behind a CDN (like Vercel Edge Network, Cloudflare, or similar), this response header can trick the CDN into caching what should have been a user-specific, private page.

Result:
A private page (your admin dashboard, etc.) could be served to other users!

How Is This Exploited? (With Example)

Let's look at an example using code and curl. Suppose you have an SSR dashboard page at /dashboard that shows user info.

dashboard.tsx (simplified)

// pages/dashboard.tsx
import { getSession } from '../lib/auth';
export async function getServerSideProps(context) {
  const session = await getSession(context.req);
  if (!session) { return { redirect: { destination: '/login' } }; }
  return { props: { user: session.user } };
}
export default function Dashboard({ user }) {
  return <div>Hello, {user.email}!</div>;
}

When a normal request is made, each user gets only their data.

An attacker can send a modified HTTP request, for example

curl -H "x-nextjs-data: 1" https://your-app.com/dashboard

(Different headers or odd query parameters can be involved, depending on how your deployment is set up.)

Now, Next.js responds with

HTTP/2 200 OK
Cache-Control: s-maxage=1, stale-while-revalidate

And caches the response!

When another user accesses /dashboard before the cache expires, they may see the attacker's page instead of their own. This is cache poisoning.

The problematic header

Cache-Control: s-maxage=1, stale-while-revalidate

stale-while-revalidate allows serving stale content while revalidating in the background.

Some CDNs and edge caches will aggressively cache pages with this header, even for URLs where each user should get *their* own page.

All later versions

There are no recommended workarounds.
You should upgrade Next.js immediately.

> How to upgrade
>

> npm install next@latest
> # or
> yarn add next@latest
> 

Also, ensure your cache/CDN is cleared or purged after patching, so poisoned pages are no longer served.

References and Further Reading

- GitHub Security Advisory: GHSA-47rf-q295-jx34
- NVD - CVE-2024-46982
- Next.js Release Notes
- Vercel Changelog (May 2024)

# FAQ

Q: Are dynamic routes affected?
A: No. Only non-dynamic SSR pages in the Pages router.

Q: Does this affect the new App Router?
A: No. Only the older Pages router (/pages/ directory).

Review your CDN and clear any existing caches after patching.

> Don't put it off—upgrade Next.js now to keep your app and user data protected.

Timeline

Published on: 09/17/2024 22:15:02 UTC
Last modified on: 09/20/2024 12:30:51 UTC