GHSA-J3R3-MXQP-R2P4
Vulnerability from github – Published: 2026-09-28 20:39 – Updated: 2026-09-28 20:39Summary
An XSS vulnerability exists in @angular/platform-server during server-side rendering (SSR) HTML serialization of ProcessingInstruction DOM nodes (<?target data?>, nodeType === 7) when nested inside fallback raw-content elements (<noscript>, <iframe>, <noembed>, <noframes>). While processing instruction data escaped > to >, it did not check for or escape matching closing tags of ancestor fallback elements (e.g., </noscript>). When rendered in a browser with scripting enabled, an unescaped closing tag sequence in a processing instruction prematurely closes the fallback raw-content tag and causes subsequent sibling elements to execute as live HTML.
Technical Description
In HTML5 parsing, fallback raw-content elements (<noscript>, <iframe>, <noembed>, <noframes>) place the browser's HTML tokenizer into RAWTEXT mode. In RAWTEXT mode, processing instruction tokens (<?...?>) are treated as literal raw text rather than bogus comments, and the parser ignores > or ?>. The only token sequence that terminates the container is an end tag matching the container tag name (`
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "22.0.0"
},
{
"fixed": "22.1.4"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "21.0.0"
},
{
"fixed": "21.2.22"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "20.0.0"
},
{
"fixed": "20.3.30"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "19.2.25"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-88058"
],
"database_specific": {
"cwe_ids": [
"CWE-116",
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-28T20:39:26Z",
"nvd_published_at": "2026-09-10T19:17:41Z",
"severity": "HIGH"
},
"details": "### Summary\nAn XSS vulnerability exists in `@angular/platform-server` during server-side rendering (SSR) HTML serialization of `ProcessingInstruction` DOM nodes (`\u003c?target data?\u003e`, `nodeType === 7`) when nested inside fallback raw-content elements (`\u003cnoscript\u003e`, `\u003ciframe\u003e`, `\u003cnoembed\u003e`, `\u003cnoframes\u003e`). While processing instruction data escaped `\u003e` to `\u0026gt;`, it did not check for or escape matching closing tags of ancestor fallback elements (e.g., `\u003c/noscript\u003e`). When rendered in a browser with scripting enabled, an unescaped closing tag sequence in a processing instruction prematurely closes the fallback raw-content tag and causes subsequent sibling elements to execute as live HTML.\n\n### Technical Description\nIn HTML5 parsing, fallback raw-content elements (`\u003cnoscript\u003e`, `\u003ciframe\u003e`, `\u003cnoembed\u003e`, `\u003cnoframes\u003e`) place the browser\u0027s HTML tokenizer into `RAWTEXT` mode. In `RAWTEXT` mode, processing instruction tokens (`\u003c?...?\u003e`) are treated as literal raw text rather than bogus comments, and the parser ignores `\u003e` or `?\u003e`. The only token sequence that terminates the container is an end tag matching the container tag name (`\u003c/noscript`, `\u003c/iframe`, etc.).\n\nDuring server-side HTML serialization, processing instruction nodes previously only replaced `\u003e` with `\u0026gt;` (preventing bogus comment breakouts in normal HTML data states) but left `\u003c` untouched. Crucially, processing instruction serialization never inspected ancestor fallback raw-content tags. As a result, if a `ProcessingInstruction` node inside `\u003cnoscript\u003e` contained `\u003c/noscript ` in its data payload, it was emitted unescaped as `\u003c?x \u003c/noscript ?\u003e`.\n\n### Impact \u0026 Reachability\n* **Reachability**: Processing instruction nodes cannot be authored directly through standard Angular template syntax (which parses `\u003c?...\u003e` into comment nodes in DOM position). Reaching this vulnerability requires application or library code calling `inject(DOCUMENT).createProcessingInstruction(target, data)` or `Renderer2` DOM insertion methods with untrusted user input passed to `data` inside a fallback raw-content container.\n* **Impact**: In applications that programmatically construct processing instruction nodes inside fallback elements during server-side rendering, an attacker controlling the processing instruction data can break out of the container and execute arbitrary JavaScript in victims\u0027 browsers.\n\n### Proof of Concept (Minimal Reproduction)\n```ts\nimport { Component, ElementRef, Renderer2, inject, DOCUMENT, AfterViewInit } from \u0027@angular/core\u0027;\n\n@Component({\n selector: \u0027app-root\u0027,\n standalone: true,\n template: `\u003cnoscript id=\"host\"\u003e\u003c/noscript\u003e`\n})\nexport class AppComponent implements AfterViewInit {\n private r = inject(Renderer2);\n private el = inject(ElementRef);\n private doc = inject(DOCUMENT);\n\n ngAfterViewInit() {\n const host = this.el.nativeElement.querySelector(\u0027#host\u0027);\n \n // Attacker-controlled input passed as Processing Instruction data\n const pi = this.doc.createProcessingInstruction(\u0027x\u0027, \u0027\u003c/noscript \u0027);\n this.r.appendChild(host, pi);\n \n // Sibling markup that should remain inert inside \u003cnoscript\u003e\n const img = this.r.createElement(\u0027img\u0027);\n this.r.setAttribute(img, \u0027src\u0027, \u0027x\u0027);\n this.r.setAttribute(img, \u0027onerror\u0027, \u0027alert(\"SSR_PI_XSS\")\u0027);\n this.r.appendChild(host, img);\n }\n}\n```\n**Vulnerable SSR Output**:\n```html\n\u003cnoscript\u003e\u003c?x \u003c/noscript ?\u003e\u003cimg src=\"x\" onerror=\"alert(\u0027SSR_PI_XSS\u0027)\"\u003e\u003c/noscript\u003e\n```\n\n### Workarounds\n* Avoid passing untrusted user input into `document.createProcessingInstruction(target, data)` when the node is inserted into `\u003cnoscript\u003e`, `\u003ciframe\u003e`, `\u003cnoembed\u003e`, or `\u003cnoframes\u003e` during server-side rendering.\n* Manually sanitize or replace `\u003c` with `\u0026lt;` in any untrusted data passed to processing instruction nodes on the server.",
"id": "GHSA-j3r3-mxqp-r2p4",
"modified": "2026-09-28T20:39:26Z",
"published": "2026-09-28T20:39:26Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/angular/angular/security/advisories/GHSA-j3r3-mxqp-r2p4"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-88058"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/issues/70146"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/73d8bbd27cb46495426d4132975a1355b47ad915"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/89b20568dfaee1ec8e0b3bcf1872acdddd2f4fef"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/ba3bc47b20b3d12f5eb141ec9c651373ae4d15e8"
},
{
"type": "WEB",
"url": "https://github.com/angular/domino/commit/04f987dc08ff3736b427f50941adf1722458528f"
},
{
"type": "PACKAGE",
"url": "https://github.com/angular/angular"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/releases/tag/v20.3.30"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/releases/tag/v21.2.22"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/releases/tag/v22.1.4"
}
],
"schema_version": "1.4.0",
"severity": [
{
"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 SSR: XSS via Unescaped Processing Instruction (\u003c?...?\u003e) Nodes in Fallback Raw-Content Elements"
}
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.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
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.