fix(TemplateParser): disallow event-property binding even with the NO_ERRORS_SCHEMA

closes #11026
This commit is contained in:
Victor Berchet 2016-08-25 11:13:44 -07:00
parent 1df69cb4d2
commit 1818056912
2 changed files with 42 additions and 19 deletions

View File

@ -854,6 +854,7 @@ class TemplateParseVisitor implements html.Visitor {
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue); boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName); securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
bindingType = PropertyBindingType.Property; bindingType = PropertyBindingType.Property;
this._assertNoEventBinding(boundPropertyName, sourceSpan);
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName, this._schemas)) { if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName, this._schemas)) {
let errorMsg = let errorMsg =
`Can't bind to '${boundPropertyName}' since it isn't a known property of '${elementName}'.`; `Can't bind to '${boundPropertyName}' since it isn't a known property of '${elementName}'.`;
@ -868,12 +869,7 @@ class TemplateParseVisitor implements html.Visitor {
} else { } else {
if (parts[0] == ATTRIBUTE_PREFIX) { if (parts[0] == ATTRIBUTE_PREFIX) {
boundPropertyName = parts[1]; boundPropertyName = parts[1];
if (boundPropertyName.toLowerCase().startsWith('on')) { this._assertNoEventBinding(boundPropertyName, sourceSpan);
this._reportError(
`Binding to event attribute '${boundPropertyName}' is disallowed ` +
`for security reasons, please use (${boundPropertyName.slice(2)})=...`,
sourceSpan);
}
// NB: For security purposes, use the mapped property name, not the attribute name. // NB: For security purposes, use the mapped property name, not the attribute name.
const mapPropName = this._schemaRegistry.getMappedPropName(boundPropertyName); const mapPropName = this._schemaRegistry.getMappedPropName(boundPropertyName);
securityContext = this._schemaRegistry.securityContext(elementName, mapPropName); securityContext = this._schemaRegistry.securityContext(elementName, mapPropName);
@ -906,6 +902,14 @@ class TemplateParseVisitor implements html.Visitor {
boundPropertyName, bindingType, securityContext, ast, unit, sourceSpan); boundPropertyName, bindingType, securityContext, ast, unit, sourceSpan);
} }
private _assertNoEventBinding(propName: string, sourceSpan: ParseSourceSpan): void {
if (propName.toLowerCase().startsWith('on')) {
this._reportError(
`Binding to event attribute '${propName}' is disallowed ` +
`for security reasons, please use (${propName.slice(2)})=...`,
sourceSpan, ParseErrorLevel.FATAL);
}
}
private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] { private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] {
const componentTypeNames: string[] = []; const componentTypeNames: string[] = [];

View File

@ -6,9 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA} from '@angular/core';
import {Component} from '@angular/core/src/metadata'; import {Component} from '@angular/core/src/metadata';
import {TestBed, getTestBed} from '@angular/core/testing'; import {TestBed, getTestBed} from '@angular/core/testing';
import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {DomSanitizer} from '@angular/platform-browser/src/security/dom_sanitization_service'; import {DomSanitizer} from '@angular/platform-browser/src/security/dom_sanitization_service';
@ -39,8 +40,8 @@ function declareTests({useJit}: {useJit: boolean}) {
}); });
afterEach(() => { getDOM().log = originalLog; }); afterEach(() => { getDOM().log = originalLog; });
describe('events', () => {
it('should disallow binding on*', () => { it('should disallow binding to attr.on*', () => {
const template = `<div [attr.onclick]="ctxProp"></div>`; const template = `<div [attr.onclick]="ctxProp"></div>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}}); TestBed.overrideComponent(SecuredComponent, {set: {template}});
try { try {
@ -54,6 +55,24 @@ function declareTests({useJit}: {useJit: boolean}) {
} }
}); });
it('should disallow binding to on* with NO_ERRORS_SCHEMA', () => {
const template = `<div [onclick]="ctxProp"></div>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({
schemas: [NO_ERRORS_SCHEMA]
});
;
try {
TestBed.createComponent(SecuredComponent);
throw 'Should throw';
} catch (e) {
expect(e.message).toContain(
`Template parse errors:\n` +
`Binding to event attribute 'onclick' is disallowed ` +
`for security reasons, please use (click)=... `);
}
});
});
describe('safe HTML values', function() { describe('safe HTML values', function() {
it('should not escape values marked as trusted', () => { it('should not escape values marked as trusted', () => {
const template = `<a [href]="ctxProp">Link Title</a>`; const template = `<a [href]="ctxProp">Link Title</a>`;