From 6e18d2daccd4c8ec72546955eb807e5d3f5dffae Mon Sep 17 00:00:00 2001 From: Bjarki Date: Fri, 9 Oct 2020 13:00:32 +0000 Subject: [PATCH] fix(compiler): promote constants in templates to Trusted Types (#39211) Angular treats constant values of attributes and properties in templates as secure. This means that these values are not sanitized, and are instead passed directly to the corresponding setAttribute or setProperty function. In cases where the given attribute or property is security-sensitive, this causes a Trusted Types violation. To address this, functions for promoting constant strings to each of the three Trusted Types are introduced to Angular's private codegen API. The compiler is updated to wrap constant strings with calls to these functions as appropriate when constructing the `consts` array. This is only done for security-sensitive attributes and properties, as classified by Angular's dom_security_schema. PR Close #39211 --- .../compiler/src/render3/r3_identifiers.ts | 5 ++ .../compiler/src/render3/view/template.ts | 31 +++++++++--- .../core/src/core_render3_private_export.ts | 3 ++ packages/core/src/render3/jit/environment.ts | 3 ++ .../core/src/sanitization/sanitization.ts | 47 +++++++++++++++++++ 5 files changed, 82 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 3ea863e09d..87e108d8e4 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -320,4 +320,9 @@ export class Identifiers { static sanitizeUrl: o.ExternalReference = {name: 'ɵɵsanitizeUrl', moduleName: CORE}; static sanitizeUrlOrResourceUrl: o.ExternalReference = {name: 'ɵɵsanitizeUrlOrResourceUrl', moduleName: CORE}; + static trustConstantHtml: o.ExternalReference = {name: 'ɵɵtrustConstantHtml', moduleName: CORE}; + static trustConstantScript: + o.ExternalReference = {name: 'ɵɵtrustConstantScript', moduleName: CORE}; + static trustConstantResourceUrl: + o.ExternalReference = {name: 'ɵɵtrustConstantResourceUrl', moduleName: CORE}; } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index eb92a1590c..e71b84c05d 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -568,7 +568,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const nonContentSelectAttributes = ngContent.attributes.filter(attr => attr.name.toLowerCase() !== NG_CONTENT_SELECT_ATTR); - const attributes = this.getAttributeExpressions(nonContentSelectAttributes, [], []); + const attributes = + this.getAttributeExpressions(ngContent.name, nonContentSelectAttributes, [], []); if (attributes.length > 0) { parameters.push(o.literal(projectionSlotIdx), o.literalArr(attributes)); @@ -635,7 +636,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // add attributes for directive and projection matching purposes const attributes: o.Expression[] = this.getAttributeExpressions( - outputAttrs, allOtherInputs, element.outputs, stylingBuilder, [], i18nAttrs); + element.name, outputAttrs, allOtherInputs, element.outputs, stylingBuilder, [], i18nAttrs); parameters.push(this.addAttrsToConsts(attributes)); // local refs (ex.:
) @@ -867,8 +868,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // prepare attributes parameter (including attributes used for directive matching) const [i18nStaticAttrs, staticAttrs] = partitionArray(template.attributes, hasI18nMeta); const attrsExprs: o.Expression[] = this.getAttributeExpressions( - staticAttrs, template.inputs, template.outputs, undefined /* styles */, - template.templateAttrs, i18nStaticAttrs); + NG_TEMPLATE_TAG_NAME, staticAttrs, template.inputs, template.outputs, + undefined /* styles */, template.templateAttrs, i18nStaticAttrs); parameters.push(this.addAttrsToConsts(attrsExprs)); // local refs (ex.: ) @@ -1285,8 +1286,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver * because those values are intended to always be generated as property instructions. */ private getAttributeExpressions( - renderAttributes: t.TextAttribute[], inputs: t.BoundAttribute[], outputs: t.BoundEvent[], - styles?: StylingBuilder, templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = [], + elementName: string, renderAttributes: t.TextAttribute[], inputs: t.BoundAttribute[], + outputs: t.BoundEvent[], styles?: StylingBuilder, + templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = [], i18nAttrs: (t.BoundAttribute|t.TextAttribute)[] = []): o.Expression[] { const alreadySeen = new Set(); const attrExprs: o.Expression[] = []; @@ -1296,7 +1298,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (attr.name === NG_PROJECT_AS_ATTR_NAME) { ngProjectAsAttr = attr; } - attrExprs.push(...getAttributeNameLiterals(attr.name), asLiteral(attr.value)); + attrExprs.push( + ...getAttributeNameLiterals(attr.name), trustedConstAttribute(elementName, attr)); }); // Keep ngProjectAs next to the other name, value pairs so we can verify that we match @@ -2128,6 +2131,20 @@ export function resolveSanitizationFn(context: core.SecurityContext, isAttribute } } +function trustedConstAttribute(tagName: string, attr: t.TextAttribute): o.Expression { + const value = asLiteral(attr.value); + switch (elementRegistry.securityContext(tagName, attr.name, /* isAttribute */ true)) { + case core.SecurityContext.HTML: + return o.importExpr(R3.trustConstantHtml).callFn([value], attr.valueSpan); + case core.SecurityContext.SCRIPT: + return o.importExpr(R3.trustConstantScript).callFn([value], attr.valueSpan); + case core.SecurityContext.RESOURCE_URL: + return o.importExpr(R3.trustConstantResourceUrl).callFn([value], attr.valueSpan); + default: + return value; + } +} + function isSingleElementTemplate(children: t.Node[]): children is[t.Element] { return children.length === 1 && children[0] instanceof t.Element; } diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 91a0d77b0e..07ccc60cf2 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -290,6 +290,9 @@ export { ɵɵsanitizeStyle, ɵɵsanitizeUrl, ɵɵsanitizeUrlOrResourceUrl, + ɵɵtrustConstantHtml, + ɵɵtrustConstantResourceUrl, + ɵɵtrustConstantScript, } from './sanitization/sanitization'; export { noSideEffects as ɵnoSideEffects, diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 894fe7711a..9f028b1d43 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -166,4 +166,7 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵsanitizeScript': sanitization.ɵɵsanitizeScript, 'ɵɵsanitizeUrl': sanitization.ɵɵsanitizeUrl, 'ɵɵsanitizeUrlOrResourceUrl': sanitization.ɵɵsanitizeUrlOrResourceUrl, + 'ɵɵtrustConstantHtml': sanitization.ɵɵtrustConstantHtml, + 'ɵɵtrustConstantScript': sanitization.ɵɵtrustConstantScript, + 'ɵɵtrustConstantResourceUrl': sanitization.ɵɵtrustConstantResourceUrl, }))(); diff --git a/packages/core/src/sanitization/sanitization.ts b/packages/core/src/sanitization/sanitization.ts index b32c1e1c3e..49b151a1e0 100644 --- a/packages/core/src/sanitization/sanitization.ts +++ b/packages/core/src/sanitization/sanitization.ts @@ -10,6 +10,8 @@ import {getDocument} from '../render3/interfaces/document'; import {SANITIZER} from '../render3/interfaces/view'; import {getLView} from '../render3/state'; import {renderStringify} from '../render3/util/misc_utils'; +import {TrustedHTML, TrustedScript, TrustedScriptURL} from '../util/security/trusted_type_defs'; +import {trustedHTMLFromString, trustedScriptFromString, trustedScriptURLFromString} from '../util/security/trusted_types'; import {allowSanitizationBypassAndThrow, BypassType, unwrapSafeValue} from './bypass'; import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer'; @@ -139,6 +141,51 @@ export function ɵɵsanitizeScript(unsafeScript: any): string { throw new Error('unsafe value used in a script context'); } +/** + * Promotes the given constant string to a TrustedHTML. + * @param html constant string containing trusted HTML. + * @returns TrustedHTML wrapping `html`. + * + * @security This is a security-sensitive function and should only be used to + * convert constant values of attributes and properties found in + * application-provided Angular templates to TrustedHTML. + * + * @codeGenApi + */ +export function ɵɵtrustConstantHtml(html: string): TrustedHTML|string { + return trustedHTMLFromString(html); +} + +/** + * Promotes the given constant string to a TrustedScript. + * @param script constant string containing a trusted script. + * @returns TrustedScript wrapping `script`. + * + * @security This is a security-sensitive function and should only be used to + * convert constant values of attributes and properties found in + * application-provided Angular templates to TrustedScript. + * + * @codeGenApi + */ +export function ɵɵtrustConstantScript(script: string): TrustedScript|string { + return trustedScriptFromString(script); +} + +/** + * Promotes the given constant string to a TrustedScriptURL. + * @param url constant string containing a trusted script URL. + * @returns TrustedScriptURL wrapping `url`. + * + * @security This is a security-sensitive function and should only be used to + * convert constant values of attributes and properties found in + * application-provided Angular templates to TrustedScriptURL. + * + * @codeGenApi + */ +export function ɵɵtrustConstantResourceUrl(url: string): TrustedScriptURL|string { + return trustedScriptURLFromString(url); +} + /** * Detects which sanitizer to use for URL property, based on tag name and prop name. *