GHSA-RGJC-H3X7-9MWG

Vulnerability from github – Published: 2026-06-15 15:16 – Updated: 2026-07-15 22:04
VLAI
Summary
Angular Client Hydration DOM Clobbering & Response-Cache Poisoning
Details

To optimize client-side bootstrap in Server-Side Rendered (SSR) environments, Angular supports Hydration via provideClientHydration(). During SSR, Angular serializes the application's runtime state (such as cached HttpClient responses) and outputs it into the HTML stream as a <script> tag with a predictable identifier:

<script type="application/json" id="ng-state">
    {"some-api-url": {"body": ...}}
</script>
````

During client bootstrap, Angular recovers this state by looking up the element via `document.getElementById('ng-state')` and parsing its text content.

Because the DOM element lookup for the state container is predictable and relies solely on the ID selector (`ng-state`), it is susceptible to **DOM Clobbering**.

If the application binds untrusted user input or CMS content to element properties such as `id` (e.g., `<div [id]="userInput">` or `<a id="ng-state">`) *before* the genuine `<script>` tag is parsed by the browser, the attacker-controlled element takes precedence in the DOM lookup.

During hydration, when Angular calls `document.getElementById('ng-state')`, the browser returns the attacker's clobbered element. Angular then attempts to parse the text content or attributes of this clobbered element as JSON.

### Impact

By clobbering the state element, the attacker can inject a custom JSON payload into Angular's `TransferState` cache. The most critical exploitation vector is poisoning the **HTTP Transfer Cache**.

1. The attacker injects a clobbered `ng-state` element containing custom JSON.  
2. The JSON maps a key (representing a target API endpoint URL) to a malicious payload of the attacker's choice.  
3. During client-side initialization, Angular's `HttpClient` checks `TransferState` before making requests. Finding the poisoned key, `HttpClient` returns the forged response instantly instead of requesting the genuine backend API.

Depending on how the application processes and renders the affected API response, this can lead to:

* **DOM-based Cross-Site Scripting (XSS)** if poisoned fields are rendered using unsafe bindings.  
* **Privilege Escalation** by spoofing user info or session details retrieved from poisoned API payloads.  
* **UI Hijacking** and redirection by spoofing configuration endpoints.

### Patched Versions

* 22.0.1  
* 21.2.17  
* 20.3.25

### Workarounds

If you cannot immediately update to a patched Angular version, apply the following workarounds:

#### A. Avoid Dynamic/User-Controlled IDs

Avoid binding raw user-supplied values or dynamic CMS IDs directly to element attributes. If dynamic IDs are required, sanitize them or prepend a static safe prefix:

```html
<!-- Vulnerable Pattern -->
<div [id]="userControlledInput">...</div>

<!-- Mitigated Pattern -->
<div [id]="'safe-prefix-' + userControlledInput">...</div>

B. Configure a Custom Application ID

Declaring a unique, non-predictable APP_ID changes the ID suffix of the state element, making it harder for attackers to predict and target:

// app.config.ts

import { APP_ID } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';

export const appConfig = {
  providers: [
    { provide: APP_ID, useValue: 'unique-obfuscated-app-id' },
    provideClientHydration()
  ]
};

This changes the state element lookup ID from ng-state to unique-obfuscated-app-id-state.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@angular/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "22.0.0-next.0"
            },
            {
              "fixed": "22.0.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@angular/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "21.0.0-next.0"
            },
            {
              "fixed": "21.2.17"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@angular/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "20.0.0-next.0"
            },
            {
              "fixed": "20.3.25"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@angular/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "19.2.25"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-54267"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-471",
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-15T15:16:18Z",
    "nvd_published_at": "2026-06-22T16:16:39Z",
    "severity": "HIGH"
  },
  "details": "To optimize client-side bootstrap in Server-Side Rendered (SSR) environments, Angular supports **Hydration** via `provideClientHydration()`. During SSR, Angular serializes the application\u0027s runtime state (such as cached `HttpClient` responses) and outputs it into the HTML stream as a `\u003cscript\u003e` tag with a predictable identifier:\n\n```html\n\u003cscript type=\"application/json\" id=\"ng-state\"\u003e\n    {\"some-api-url\": {\"body\": ...}}\n\u003c/script\u003e\n````\n\nDuring client bootstrap, Angular recovers this state by looking up the element via `document.getElementById(\u0027ng-state\u0027)` and parsing its text content.\n\nBecause the DOM element lookup for the state container is predictable and relies solely on the ID selector (`ng-state`), it is susceptible to **DOM Clobbering**.\n\nIf the application binds untrusted user input or CMS content to element properties such as `id` (e.g., `\u003cdiv [id]=\"userInput\"\u003e` or `\u003ca id=\"ng-state\"\u003e`) *before* the genuine `\u003cscript\u003e` tag is parsed by the browser, the attacker-controlled element takes precedence in the DOM lookup.\n\nDuring hydration, when Angular calls `document.getElementById(\u0027ng-state\u0027)`, the browser returns the attacker\u0027s clobbered element. Angular then attempts to parse the text content or attributes of this clobbered element as JSON.\n\n### Impact\n\nBy clobbering the state element, the attacker can inject a custom JSON payload into Angular\u0027s `TransferState` cache. The most critical exploitation vector is poisoning the **HTTP Transfer Cache**.\n\n1. The attacker injects a clobbered `ng-state` element containing custom JSON.  \n2. The JSON maps a key (representing a target API endpoint URL) to a malicious payload of the attacker\u0027s choice.  \n3. During client-side initialization, Angular\u0027s `HttpClient` checks `TransferState` before making requests. Finding the poisoned key, `HttpClient` returns the forged response instantly instead of requesting the genuine backend API.\n\nDepending on how the application processes and renders the affected API response, this can lead to:\n\n* **DOM-based Cross-Site Scripting (XSS)** if poisoned fields are rendered using unsafe bindings.  \n* **Privilege Escalation** by spoofing user info or session details retrieved from poisoned API payloads.  \n* **UI Hijacking** and redirection by spoofing configuration endpoints.\n\n### Patched Versions\n\n* 22.0.1  \n* 21.2.17  \n* 20.3.25\n\n### Workarounds\n\nIf you cannot immediately update to a patched Angular version, apply the following workarounds:\n\n#### A. Avoid Dynamic/User-Controlled IDs\n\nAvoid binding raw user-supplied values or dynamic CMS IDs directly to element attributes. If dynamic IDs are required, sanitize them or prepend a static safe prefix:\n\n```html\n\u003c!-- Vulnerable Pattern --\u003e\n\u003cdiv [id]=\"userControlledInput\"\u003e...\u003c/div\u003e\n\n\u003c!-- Mitigated Pattern --\u003e\n\u003cdiv [id]=\"\u0027safe-prefix-\u0027 + userControlledInput\"\u003e...\u003c/div\u003e\n```\n\n#### B. Configure a Custom Application ID\n\nDeclaring a unique, non-predictable `APP_ID` changes the ID suffix of the state element, making it harder for attackers to predict and target:\n\n```ts\n// app.config.ts\n\nimport { APP_ID } from \u0027@angular/core\u0027;\nimport { provideClientHydration } from \u0027@angular/platform-browser\u0027;\n\nexport const appConfig = {\n  providers: [\n    { provide: APP_ID, useValue: \u0027unique-obfuscated-app-id\u0027 },\n    provideClientHydration()\n  ]\n};\n\n```\n\nThis changes the state element lookup ID from `ng-state` to `unique-obfuscated-app-id-state`.",
  "id": "GHSA-rgjc-h3x7-9mwg",
  "modified": "2026-07-15T22:04:05Z",
  "published": "2026-06-15T15:16:18Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/angular/angular/security/advisories/GHSA-rgjc-h3x7-9mwg"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54267"
    },
    {
      "type": "WEB",
      "url": "https://github.com/angular/angular/pull/69064"
    },
    {
      "type": "WEB",
      "url": "https://github.com/angular/angular/commit/6bde84fa8e6a5770b54040fbbc9bf10d5d0386fa"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/angular/angular"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Angular Client Hydration DOM Clobbering \u0026 Response-Cache Poisoning"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

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.

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.


Loading…