Visitar URL original
fix(platform-browser): prevent XSS through style binding DOM clobbering · angular/angular@4a1bf85 · GitHub
Skip to content

Commit 4a1bf85

Browse files
committed
fix(platform-browser): prevent XSS through style binding DOM clobbering
Recover the intrinsic CSSStyleDeclaration before applying or removing style bindings. HTML named-property resolution can otherwise make a form control named or identified as style shadow HTMLFormElement.style. This lets attacker-controlled keys from style bindings reach unrelated DOM sinks such as innerHTML and outerHTML, enabling same-origin script execution during rendering. Fixes #70021
1 parent 97e6aa4 commit 4a1bf85

11 files changed

Lines changed: 241 additions & 9 deletions

File tree

‎integration/defer/size.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"dist/main.js": 12709,
3-
"dist/polyfills.js": 35677,
4-
"dist/defer.component-[hash].js": 345
2+
"dist/main.js": 13425,
3+
"dist/polyfills.js": 35784,
4+
"dist/defer.component-[hash].js": 331
55
}

‎packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@
547547
"getRuntimeErrorCode",
548548
"getSelectedIndex",
549549
"getSelectedTNode",
550+
"getStyleDeclaration",
550551
"getStyleHost",
551552
"getTNode",
552553
"getTNodeFromLView",

‎packages/core/test/bundling/create_component/bundle.golden_symbols.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@
451451
"getRuntimeErrorCode",
452452
"getSelectedIndex",
453453
"getSelectedTNode",
454+
"getStyleDeclaration",
454455
"getStyleHost",
455456
"getTNode",
456457
"getTNodeFromLView",

‎packages/core/test/bundling/defer/bundle.golden_symbols.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"errorHandler",
4747
"getBaseElementHref",
4848
"getDOM",
49+
"getStyleDeclaration",
4950
"initDomAdapter",
5051
"isTemplateNode",
5152
"parseCookieValue",

‎packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@
649649
"getSelectedIndex",
650650
"getSelectedTNode",
651651
"getSimpleChangesStore",
652+
"getStyleDeclaration",
652653
"getStyleHost",
653654
"getSuperType",
654655
"getSymbolIterator",

‎packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@
645645
"getSelectedIndex",
646646
"getSelectedTNode",
647647
"getSimpleChangesStore",
648+
"getStyleDeclaration",
648649
"getStyleHost",
649650
"getSuperType",
650651
"getSymbolIterator",

‎packages/core/test/bundling/hydration/bundle.golden_symbols.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,7 @@
626626
"getSegmentHead",
627627
"getSelectedIndex",
628628
"getSerializedContainerViews",
629+
"getStyleDeclaration",
629630
"getStyleHost",
630631
"getSymbolIterator",
631632
"getTDeferBlockDetails",

‎packages/core/test/bundling/router/bundle.golden_symbols.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,7 @@
741741
"getSelectedIndex",
742742
"getSelectedTNode",
743743
"getSimpleChangesStore",
744+
"getStyleDeclaration",
744745
"getStyleHost",
745746
"getSymbolIterator",
746747
"getTNode",

‎packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@
412412
"getRootTViewTemplate",
413413
"getRuntimeErrorCode",
414414
"getSelectedIndex",
415+
"getStyleDeclaration",
415416
"getStyleHost",
416417
"getTNode",
417418
"getTNodeFromLView",

‎packages/platform-browser/src/dom/dom_renderer.ts‎

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,15 @@ class DefaultDomRenderer2 implements Renderer2 {
440440
if (isVariable) {
441441
style = style.replace('%NS%', this.cssVarNamespace);
442442
}
443+
const styleDeclaration = getStyleDeclaration(el);
443444
if (isVariable || flags & (RendererStyleFlags2.DashCase | RendererStyleFlags2.Important)) {
444-
el.style.setProperty(style, value, flags & RendererStyleFlags2.Important ? 'important' : '');
445+
styleDeclaration.setProperty(
446+
style,
447+
value,
448+
flags & RendererStyleFlags2.Important ? 'important' : '',
449+
);
445450
} else {
446-
el.style[style] = value;
451+
(styleDeclaration as any)[style] = value;
447452
}
448453
}
449454

@@ -452,11 +457,12 @@ class DefaultDomRenderer2 implements Renderer2 {
452457
if (isVariable) {
453458
style = style.replace('%NS%', this.cssVarNamespace);
454459
}
460+
const styleDeclaration = getStyleDeclaration(el);
455461
if (isVariable || flags & RendererStyleFlags2.DashCase) {
456462
// removeProperty has no effect when used on camelCased properties.
457-
el.style.removeProperty(style);
463+
styleDeclaration.removeProperty(style);
458464
} else {
459-
el.style[style] = '';
465+
(styleDeclaration as any)[style] = '';
460466
}
461467
}
462468

@@ -562,6 +568,32 @@ function isTemplateNode(node: any): node is HTMLTemplateElement {
562568
return node.tagName === 'TEMPLATE' && node.content !== undefined;
563569
}
564570

571+
function getStyleDeclaration(el: any): CSSStyleDeclaration {
572+
const style = el.style;
573+
// Named form controls expose an element or RadioNodeList here,
574+
// neither of which has these methods.
575+
if (
576+
style != null &&
577+
typeof style.setProperty === 'function' &&
578+
typeof style.removeProperty === 'function'
579+
) {
580+
return style;
581+
}
582+
583+
for (
584+
let prototype = Object.getPrototypeOf(el);
585+
prototype;
586+
prototype = Object.getPrototypeOf(prototype)
587+
) {
588+
const styleGetter = Object.getOwnPropertyDescriptor(prototype, 'style')?.get;
589+
if (styleGetter) {
590+
// Form-associated controls can shadow inherited properties through named property access.
591+
return styleGetter.call(el);
592+
}
593+
}
594+
return el.style;
595+
}
596+
565597
class ShadowDomRenderer extends DefaultDomRenderer2 {
566598
private readonly shadowRoot: ShadowRoot;
567599

0 commit comments

Comments
 (0)