Code in Client-side template

April 12, 2019 · View on GitHub

Some substrings are specially interpreted by client-side template. For example, {{...}} might contain an expression or binding in Angular JS or Polymer.

This document collects information about substrings that we need to defang to prevent a sanitized substring from being interpreted as high-privilege code by a client-side template.

Client-side template substrings that appear in Text Nodes

Many client-side templates look for special constructs in text nodes. Often, using character references ({) will not affect interpretation since the client-side template is evaluated by JavaScript operating on the DOM after the HTML parser has decoded character references.

Template LanguageConstructExampleNotes
Angular{{...}}{{buttonText}}
Polymer{{...}}{{arrayOfFriends | startsWith('M')}}
CanJS<%...%><% alert(1) %>
Underscore<%...%><% alert(1) %>
Ember{{...}}{{#view tagName=script}}alert(2){{/view}}
Ractive{{...}}{{#1..constructor.constructor('alert(1)')():num}}
JsRenderer{{...}}{{:constructor.constructor('alert(2)')()}}
KendoUI#...## alert(1) #
Vanilla ES6 `${...}` `${alert(1)}`

Client side template / expression attributes

When filtering client-side templates, it should also be considered to fully cover attributes containing expressions and parseable information that might cause damage or lead to arbitrary JavaScript execution.

Template LanguageAttrbutesNotes
Angularng-xxx, ng:xxx, data-ng-xxx, x-ng-xxxAngular normalizes attribute names before parsing their contents, making it impossible to work with blacklists. Further, Angular allows to e.g. fetch imports form within a class attribute. This means, we should also consider filtering contents.
Vuev-xxx
Knockoutdata-xxx
Emberdata-xxx

Escaping of sensitive constructs

SubstringDefangsPCDATA ReplacementRCDATA Replacement†Notes
{ ({ / end-of-input){{...}}{<!-- -->{ U+200BComment breaks text nodes. U+200B is a zero-width space and is not semantics preserving in RCDATA

† - RCDATA is the content type of <title> and <textarea> elements. These often do not appear in sanitized text, but can be harder to defang.

TODO: do we need to consider <![CDATA[...]]> and foreign XML contexts.

TODO: what do client side templates do with comments in the DOM?

Test-cases

Current snapshot of DOMPurify test-cases (to be updated, please feel free to reorganize):

assert.equal( DOMPurify.sanitize( '<a>123{{456}}<b><style><% alert(1) %></style>456</b></a>', {SAFE_FOR_TEMPLATES: true}), "<a> <b><style> </style>456</b></a>" );
assert.equal( DOMPurify.sanitize( '<a data-bind="style: alert(1)"></a>', {SAFE_FOR_TEMPLATES: true}), "<a></a>" );
assert.equal( DOMPurify.sanitize( '<a data-harmless=""></a>', {SAFE_FOR_TEMPLATES: true, ALLOW_DATA_ATTR: true}), "<a></a>" );
assert.equal( DOMPurify.sanitize( '<a data-harmless=""></a>', {SAFE_FOR_TEMPLATES: false, ALLOW_DATA_ATTR: false}), "<a></a>" );
assert.equal( DOMPurify.sanitize( '<a>{{123}}{{456}}<b><style><% alert(1) %><% 123 %></style>456</b></a>', {SAFE_FOR_TEMPLATES: true}), "<a> <b><style> </style>456</b></a>" );
assert.equal( DOMPurify.sanitize( '<a>{{123}}abc{{456}}<b><style><% alert(1) %>def<% 123 %></style>456</b></a>', {SAFE_FOR_TEMPLATES: true}), "<a> <b><style> </style>456</b></a>" );
assert.equal( DOMPurify.sanitize( '<a>123{{45{{6}}<b><style><% alert(1)%> %></style>456</b></a>', {SAFE_FOR_TEMPLATES: true}), "<a> <b><style> </style>456</b></a>" );
assert.equal( DOMPurify.sanitize( '<a>123{{45}}6}}<b><style><% <%alert(1) %></style>456</b></a>', {SAFE_FOR_TEMPLATES: true}), "<a> <b><style> </style>456</b></a>" );
assert.equal( DOMPurify.sanitize( '<a>123{{<b>456}}</b><style><% alert(1) %></style>456</a>', {SAFE_FOR_TEMPLATES: true}), "<a>123 <b> </b><style> </style>456</a>" );
assert.equal( DOMPurify.sanitize( '<b>{{evil<script>alert(1)</script><form><img src=x name=textContent></form>}}</b>', {SAFE_FOR_TEMPLATES: true}), "<b>  </b>" );
assert.equal( DOMPurify.sanitize( '<b>he{{evil<script>alert(1)</script><form><img src=x name=textContent></form>}}ya</b>', {SAFE_FOR_TEMPLATES: true}), "<b>he  ya</b>" );