GHSA-2X6F-RP82-PPMJ

Vulnerability from github – Published: 2026-10-15 20:15 – Updated: 2026-10-15 20:15
VLAI
Summary
Nuxt OG Image cache poisoning through derivable cache keys on unsigned dynamic image routes
Details

Vendor: Nuxt Product: Nuxt OG Image Version: 6.2.5 Description: Due to the specifics of caching and key generation in the Open Graph image cache created by the module, an unauthenticated attacker can modify the content of the image (for example, the title parameter) that is delivered to the user. Impact: Content Spoofing Vulnerable component: route /_og/d/*.png Exploitation conditions: Access to the application Mitigation: Upgrade to 6.7.2 and set a sufficiently secure secret in config ogImage Researcher: Dmitry Vanin (Positive Technologies)

Research

During the previous vulnerability research, it was discovered that version 6.2.5 changed the mechanism for generating cache indexes; it now takes into account the parameters passed during image generation.

However, the image is still generated from data transmitted to the attacker.

Listing 1. New code for generating indexes in cache

export function resolvePathCacheKey(e: H3Event, path: string, resolvedOptions?: Record<string, any>) {
  const siteConfig = getSiteConfig(e, {
    resolveRefs: true,
  })
  const basePath = withoutTrailingSlash(withoutLeadingSlash(normalizeKey(path)))
  const hashParts: any[] = [
    basePath,
    import.meta.prerender ? '' : siteConfig.url,
  ]
  // Hash resolved options (not raw query string) so unknown/extra query params
  // cannot produce unique cache keys and bypass the cache.
  if (resolvedOptions)
    hashParts.push(hash(resolvedOptions))
  return [
    (!basePath || basePath === '/') ? 'index' : basePath,
    hash(hashParts),
  ].join(':')
}

In this case, basePath is obtained from the _path parameter, resolvedOptions are the normalized settings of the component, for example:

Listing 2. An example of resolvedOptions

{"emojis":"noto","extension":"png","width":1200,"height":600,"cacheMaxAgeSeconds":259200,"component":"OgImageDefaultImageTakumi"}

From the analysis of resolvedOptions, it was concluded that, in general, all parameters are known either from the direct link to the OG Image or from the server response.

Vulnerability reproduction

  1. Find the route to OG Image. Figure 1. Demonstration of finding the route to OG Image image

  2. Retrieve parameters from the server’s response, especially cacheMaxAgeSeconds (cache-control header, s-maxage key), the other parameters are known from the meta tags as well as from the c_<component name> parameter. Figure 2. Obtaining parameters image

  3. Generate a cache key using a script. Listing 3. Srcipt

import {hash} from "ohash";

var url = "https://contoso.com"

var obj = JSON.parse(`{"emojis":"noto","extension":"png","width":1200,"height":600,"cacheMaxAgeSeconds":259200,"component":"OgImageBlogPostTakumi"}`)

var basePath = '/'

var hashParts = [
    basePath,
    url
  ]

if (obj)
    hashParts.push(hash(obj))

console.log([
    (!basePath || basePath === '/') ? 'index' : basePath,
    hash(hashParts),
  ].join(':'))
  1. Repeat the steps as in the vulnerability 1, but transmit the obtained cacheKey in addition.
  2. Thus, the same result as in the vulnerability 1 was obtained. Figure 4. Server response image

Credits

Researcher: Dmitry Vanin (Positive Technologies)

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "nuxt-og-image"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "6.2.5"
            },
            {
              "fixed": "6.7.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-102527"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-349"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-15T20:15:27Z",
    "nvd_published_at": "2026-10-15T17:17:08Z",
    "severity": "MODERATE"
  },
  "details": "**Vendor:** Nuxt\n**Product:** Nuxt OG Image\n**Version:** 6.2.5\n**Description:** Due to the specifics of caching and key generation in the Open Graph image cache created by the module, an unauthenticated attacker can modify the content of the image (for example, the `title` parameter) that is delivered to the user. \n**Impact:** Content Spoofing\n**Vulnerable component:** route `/_og/d/*.png`\n**Exploitation conditions:** Access to the application\n**Mitigation:** Upgrade to 6.7.2 and set a sufficiently secure `secret` in config `ogImage`\n**Researcher:** Dmitry Vanin (Positive Technologies)\n\n## Research\nDuring [the previous vulnerability research](https://github.com/nuxt-modules/og-image/security/advisories/GHSA-6c86-prf5-hj4h), it was discovered that version 6.2.5 changed the mechanism for generating cache indexes; it now takes into account the parameters passed during image generation.\n\nHowever, the image is still generated from data transmitted to the attacker.\n\n_Listing 1. New code for generating indexes in cache_\n```\nexport function resolvePathCacheKey(e: H3Event, path: string, resolvedOptions?: Record\u003cstring, any\u003e) {\n  const siteConfig = getSiteConfig(e, {\n    resolveRefs: true,\n  })\n  const basePath = withoutTrailingSlash(withoutLeadingSlash(normalizeKey(path)))\n  const hashParts: any[] = [\n    basePath,\n    import.meta.prerender ? \u0027\u0027 : siteConfig.url,\n  ]\n  // Hash resolved options (not raw query string) so unknown/extra query params\n  // cannot produce unique cache keys and bypass the cache.\n  if (resolvedOptions)\n    hashParts.push(hash(resolvedOptions))\n  return [\n    (!basePath || basePath === \u0027/\u0027) ? \u0027index\u0027 : basePath,\n    hash(hashParts),\n  ].join(\u0027:\u0027)\n}\n```\nIn this case, `basePath` is obtained from the `_path` parameter, `resolvedOptions` are the normalized settings of the component, for example:\n\n_Listing 2. An example of `resolvedOptions`_\n```\n{\"emojis\":\"noto\",\"extension\":\"png\",\"width\":1200,\"height\":600,\"cacheMaxAgeSeconds\":259200,\"component\":\"OgImageDefaultImageTakumi\"}\n```\nFrom the analysis of `resolvedOptions`, it was concluded that, in general, all parameters are known either from the direct link to the OG Image or from the server response.\n\n## Vulnerability reproduction\n\n1. Find the route to OG Image.\n_Figure 1. Demonstration of finding the route to OG Image_\n\u003cimg width=\"974\" height=\"361\" alt=\"image\" src=\"https://github.com/user-attachments/assets/d3ee6b54-d545-4a3b-8cbf-f82df99ed4d0\" /\u003e\n\n2. Retrieve parameters from the server\u2019s response, especially `cacheMaxAgeSeconds` (`cache-control` header, `s-maxage` key), the other parameters are known from the meta tags as well as from the `c_\u003ccomponent name\u003e` parameter.\n_Figure 2. Obtaining parameters_\n\u003cimg width=\"974\" height=\"245\" alt=\"image\" src=\"https://github.com/user-attachments/assets/84a4cd8b-9281-47ef-b524-0047a0f6077a\" /\u003e\n\n3. Generate a cache key using a script.\n_Listing 3. Srcipt_\n```\nimport {hash} from \"ohash\";\n\nvar url = \"https://contoso.com\"\n\nvar obj = JSON.parse(`{\"emojis\":\"noto\",\"extension\":\"png\",\"width\":1200,\"height\":600,\"cacheMaxAgeSeconds\":259200,\"component\":\"OgImageBlogPostTakumi\"}`)\n\nvar basePath = \u0027/\u0027\n\nvar hashParts = [\n    basePath,\n    url\n  ]\n  \nif (obj)\n\thashParts.push(hash(obj))\n\nconsole.log([\n    (!basePath || basePath === \u0027/\u0027) ? \u0027index\u0027 : basePath,\n    hash(hashParts),\n  ].join(\u0027:\u0027))\n```\n4. Repeat the steps as in [the vulnerability 1](https://github.com/nuxt-modules/og-image/security/advisories/GHSA-6c86-prf5-hj4h), but transmit the obtained `cacheKey` in addition.  \n5. Thus, the same result as in [the vulnerability 1](https://github.com/nuxt-modules/og-image/security/advisories/GHSA-6c86-prf5-hj4h) was obtained.\n_Figure 4. Server response_\n\u003cimg width=\"974\" height=\"729\" alt=\"image\" src=\"https://github.com/user-attachments/assets/5895c0e8-e497-410d-bef9-f6f2f9649261\" /\u003e\n\n## Credits\nResearcher: Dmitry Vanin (Positive Technologies)",
  "id": "GHSA-2x6f-rp82-ppmj",
  "modified": "2026-10-15T20:15:28Z",
  "published": "2026-10-15T20:15:27Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/nuxt-modules/og-image/security/advisories/GHSA-2x6f-rp82-ppmj"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-102527"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nuxt-modules/og-image/pull/638"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nuxt-modules/og-image/commit/e6c5b53ce7f434ab8cbc91298dccfb2b515c56a5"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/nuxt-modules/og-image"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nuxt-modules/og-image/releases/tag/v6.7.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Nuxt OG Image cache poisoning through derivable cache keys on unsigned dynamic image routes"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.