VYPR
Moderate severityNVD Advisory· Published Oct 14, 2024· Updated Oct 15, 2024

Next.js image optimization has Denial of Service condition

CVE-2024-47831

Description

Next.js is a React Framework for the Web. Cersions on the 10.x, 11.x, 12.x, 13.x, and 14.x branches before version 14.2.7 contain a vulnerability in the image optimization feature which allows for a potential Denial of Service (DoS) condition which could lead to excessive CPU consumption. Neither the next.config.js file that is configured with images.unoptimized set to true or images.loader set to a non-default value nor the Next.js application that is hosted on Vercel are affected. This issue was fully patched in Next.js 14.2.7. As a workaround, ensure that the next.config.js file has either images.unoptimized, images.loader or images.loaderFile assigned.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
nextnpm
>= 10.0.0, < 14.2.714.2.7

Affected products

1

Patches

1
d11cbc9ff0b1

Reject next image urls in image optimizer (#68628)

https://github.com/vercel/next.jsJiachi LiuAug 8, 2024via ghsa
4 files changed · +66 8
  • packages/next/src/lib/url.ts+8 0 modified
    @@ -11,3 +11,11 @@ export function getPathname(url: string) {
     export function isFullStringUrl(url: string) {
       return /https?:\/\//.test(url)
     }
    +
    +export function parseUrl(url: string): URL | undefined {
    +  let parsed = undefined
    +  try {
    +    parsed = new URL(url, DUMMY_ORIGIN)
    +  } catch {}
    +  return parsed
    +}
    
  • packages/next/src/server/image-optimizer.ts+8 3 modified
    @@ -28,6 +28,7 @@ import type {
     import { sendEtagResponse } from './send-payload'
     import { getContentType, getExtension } from './serve-static'
     import * as Log from '../build/output/log'
    +import { parseUrl } from '../lib/url'
     
     type XCacheHeader = 'MISS' | 'HIT' | 'STALE'
     
    @@ -197,9 +198,13 @@ export class ImageOptimizerCache {
           }
         }
     
    -    if (url.startsWith('/_next/image')) {
    -      return {
    -        errorMessage: '"url" parameter cannot be recursive',
    +    const parsedUrl = parseUrl(url)
    +    if (parsedUrl) {
    +      const decodedPathname = decodeURIComponent(parsedUrl.pathname)
    +      if (/\/_next\/image($|\/)/.test(decodedPathname)) {
    +        return {
    +          errorMessage: '"url" parameter cannot be recursive',
    +        }
           }
         }
     
    
  • test/integration/image-optimizer/test/util.ts+41 5 modified
    @@ -8,6 +8,7 @@ import {
       fetchViaHTTP,
       File,
       findPort,
    +  getFetchUrl,
       killApp,
       launchApp,
       nextBuild,
    @@ -879,11 +880,46 @@ export function runTests(ctx) {
         )
       })
     
    -  it('should fail when url is recursive', async () => {
    -    const query = { url: `/_next/image?url=test.pngw=1&q=1`, w: ctx.w, q: 1 }
    -    const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {})
    -    expect(res.status).toBe(400)
    -    expect(await res.text()).toBe(`"url" parameter cannot be recursive`)
    +  describe('recursive url is not allowed', () => {
    +    it('should fail with relative next image url', async () => {
    +      const query = { url: `/_next/image?url=test.pngw=1&q=1`, w: ctx.w, q: 1 }
    +      const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {})
    +      expect(res.status).toBe(400)
    +      expect(await res.text()).toBe(`"url" parameter cannot be recursive`)
    +    })
    +
    +    it('should fail with encoded relative image url', async () => {
    +      const query = {
    +        url: '%2F_next%2Fimage%3Furl%3Dtest.pngw%3D1%26q%3D1',
    +        w: ctx.w,
    +        q: 1,
    +      }
    +      const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {})
    +      expect(res.status).toBe(400)
    +      expect(await res.text()).toBe(`"url" parameter is invalid`)
    +    })
    +
    +    it('should fail with absolute next image url', async () => {
    +      const fullUrl = getFetchUrl(
    +        ctx.appPort,
    +        '/_next/image?url=test.pngw=1&q=1'
    +      )
    +      const query = { url: fullUrl, w: ctx.w, q: 1 }
    +      const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {})
    +      expect(res.status).toBe(400)
    +      expect(await res.text()).toBe(`"url" parameter cannot be recursive`)
    +    })
    +
    +    it('should fail with relative image url with assetPrefix', async () => {
    +      const fullUrl = getFetchUrl(
    +        ctx.appPort,
    +        `/assets/_next/image?url=test.pngw=1&q=1`
    +      )
    +      const query = { url: fullUrl, w: ctx.w, q: 1 }
    +      const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, {})
    +      expect(res.status).toBe(400)
    +      expect(await res.text()).toBe(`"url" parameter cannot be recursive`)
    +    })
       })
     
       it('should fail when internal url is not an image', async () => {
    
  • test/lib/next-test-utils.ts+9 0 modified
    @@ -147,6 +147,15 @@ export function withQuery(
       return `${pathname}?${querystring}`
     }
     
    +export function getFetchUrl(
    +  appPort: string | number,
    +  pathname: string,
    +  query?: Record<string, any> | string | null | undefined
    +) {
    +  const url = query ? withQuery(pathname, query) : pathname
    +  return getFullUrl(appPort, url)
    +}
    +
     export function fetchViaHTTP(
       appPort: string | number,
       pathname: string,
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.