Name: polymer-resin
Owner: polymer
Description: XSS mitigation for Polymer webcomponents that uses safe html type contracts
Created: 2017-04-12 14:53:20.0
Updated: 2018-05-03 05:19:13.0
Pushed: 2018-01-30 20:22:51.0
Size: 406
Language: JavaScript
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
XSS mitigation for Polymer webcomponents.
This document explains what polymer-resin is. See “Getting Started” if you're interested in how to use it.
Relevant Concepts & Specs
Relevant Code
Gerrit is a code review tool that may be used by Bets to manage their codebases. Polygerrit-UI is a rewrite of the Gerrit UI using Polymer instead of Closure Templates.
Vulnerabilities in code review tools affects the integrity of the codebase – an XSS (Cross-site scripting) attack that can submit a form in a code review tool can send spurious approvals; and (depending on the level of integration with revision control) suggest edits, commit approved changes, and kick off test runs and other processes with edited files as inputs.
In most template languages, one defines templates, but in Polymer one defines custom elements. This means that there is not a closed class of HTML elements and attributes about which we can reason as there is for Closure Templates auto-sanitizer.
Make it easy for security auditors to quickly check whether a project's custom element definitions fall into a known-safe subset of Polymer.
Existing auto-escaping template systems (CTemplates, Closure Templates, and go/html/template) assume that
There is a large community of non-malicious application developers who author templates.
This community is large enough that we cannot assume that they uniformly apply rigorous secure coding and review practices.
A system worked on by N developers is vulnerable if just one of them mistakenly introduces a vulnerability. Thus, large groups of individuals who individually rarely make mistakes are nevertheless likely to produce a system that has vulnerabilities.
There is a much smaller group of expert browser implementors who define the semantics of tags and attributes. They are aware of the security consequences of their work, and the code they produce is heavily reviewed, but they produce blunt instruments.
For Polymer, we assume that
There is a large community of non-malicious custom element authors.
There is a large community of non-malicious application developers.
These two communities overlap to a great degree and are (similar to above) large enough that we cannot assume uniform rigor in applying secure practices.
The novel security consequences of webcomponents arise because they expand the ways in which unchecked values can reach builtin sinks.
A builtin sink is a property or attribute that can be set by user code and is handled specially by the browser in a manner that may have security implications, in particular, attacker-controlled script execution. Builtin sinks tend to correspond to IDL attributes that are annotated with Reflect, CustomElementCallbacks, or CEReactions.
There is a hazard whenever an unchecked value reaches a builtin sink. An
unchecked value is one that could be controlled by an attacker; it might
originate from outside an origin controlled by the
application author.
For instance, the href
attribute of an HTMLAnchorElement is a
security-relevant sink; if it can be reached by a value entirely under an
attacker's control, that attacker can execute arbitrary script in the
context of the user's browser session through injection of a javascript:
URL.
There are many ways that JavaScript can manipulate builtin sinks, and we will use JSConformance policies to guide developers towards safe patterns, and focus instead on web-component specific hazards including
<a href="[[...]]">
where [[...]]
can be controlled by an attacker;<my-custom-element onclick="[[...]]">
;<my-custom-element my-url="[[...]]">
and the custom element's shadow
DOM contains a builtin sink <template><a href="[[myUrl]]"></template>
;<a is="my-custom-link" href="[[...]]">
) or
indirectly (<a is="my-custom-link" href="[[my-url]]"
my-url="[[...]]">
)<a [[...]]="some-value">
or can specify the type of custom element <a is="[[...]]">
or
<[[...]] src="some-value">
.Our security goal is to allow element authors to write code that receives unchecked values, and routes them to builtin sinks without the risk of XSS, redirection attacks, etc. We do this by taking the burden of avoiding these attacks off the authors and reviewers of large amounts of application code and move it into a small amount of vetted infrastructure code.
It is not a goal to address direct access to builtin sinks by JavaScript (e.g.
HTMLElement.setAttribute(...)
or HTMLElement.innerHTML = ...
) as those are
well handled by existing JSConformance policies.
We could do this by tweaking Polymer to attach data provenance to properties and attributes but this would be deeply backward incompatible.
We could do this by static analysis of custom element definition, but this requires analysis of JavaScript, and our existing JS type systems are unsound, so any sound analysis would require a lot of duplication of effort or would produce many false positives.
Instead we propose to use existing property value hooks (links at top) provided by Polymer. A security auditor can then check that a Polymer project is properly configured to load these hooks, and check that the project uses JSConformance to prevent forgery of safe string values.
We provide a standalone importable HTML file polymer-resin.html
that
implements Polymer.sanitizeDOMValue
to intercept assignments to builtin sinks
given values that originate from expressions specified in Polymer HTML. We also
provide a Polymer v1 shim that checks Polymer.version
to see if it needs to
patch Polymer.Base._computeFinalAnnotationValue
to call the same sanitizer.
A security auditor should check that polymer-resin is running early in the page render process. It should load and initialize before the applications main element is instantiated so that it can intercept reflected XSS. Putting the HTML import or script load immediately after the load of framework code suffices.
When text is interpolated
ipt>
re;
terpolation]]
r;
ript>
Polymer denormalizes the DOM so that “before;
“, “[[interpolation]]
” and
“after;
” are three different text nodes, and control reaches the sanitizer
with a TextNode as the node, and a null property name.
We use this to intercept text interpolation and allow it only when the content is human-readable HTML.
Normally, when an HTML page is parsed, the browser knows that, for an <A>
element, it creates an HTMLAElement instance. The custom elements draft specification explicitly allows parsing
of <my-custom>
before the JavaScript that will eventually define and register
HTMLMyCustomElement.
d>
ipt src="webcomponents-lite.js"></script>
k rel="import" href="custom-element.html">
y>
tom-element id="app"></custom-element>
The life-cycle of a polymer app often looks like
<custom-element>
ow.addEventListener('WebComponentsReady', ?)
seems like it might run at the right time, and the 'HTMLImportsLoaded'
event
is another good candidate.
Both run after the HTML element definitions have been loaded, and before the app has been provisioned with state loaded from the server, but state that is initialized based on location or query parameters will already have reached custom elements, meaning reflected XSS is still possible.
To prevent reflected XSS, we need to initialize after Polymer is loaded, and before the first custom element definition is registered (except for those defined by Polymer internally).
polygerrit-ui/app/index.html
is a good example of a polymer app. The column on
the left shows the app before resin is added, and the column on the right shows
how we want it to work with Resin. Note that polymer.html is not explicitly
loaded by the index.html page; it's loaded via a transitive HTML import.
Without Resin | With Resin
—————————— | ————————————-
Enter <html><head>
| ditto
Load webcomponents-lite.js | ditto
| HTML import *polymer.html*
| Load and configure *polymer-resin.js*
Preload <gr-app>
definition | ditto
HTML import polymer.html |
Load other element definitions | ditto
Instantiate <gr-app>
element | ditto
We provide a polymer-resin.html, an importable HTML file that does two things.
The handler receives
and returns a safe value.
When sanitizing a property or attribute value we
0
, NaN
, false
which we deem low-risk.document.createElement(builtinElementName)
.security.html.contracts
), then
unwrap any typed string values and allow.We could break from the loop if the prototype has an own property with the given name. We could memoize the fact that we found a result with the original key if we?re willing to assume that no properties are deleted from prototypes during program execution.
The security.html.contracts
module captures builtin HTML element and attribute
relationships, and we apply the following filters.
Attribute Type | Privileged Typed String Type | Raw Value Filter ——————– | —————————- | —————- NONE | none | allow SAFE_HTML | goog.html.SafeHtml | goog.string.htmlEscape SAFE_URL | goog.html.SafeUrl | goog.html.SafeUrl.sanitize TRUSTED_RESOURCE_URL | goog.html.TrustedResourceUrl | none SAFE_STYLE | goog.html.SafeStyle | reject SAFE_SCRIPT | goog.html.SafeScript | reject ENUM | none | whitelist per element/attribute CONSTANT | goog.string.Const | reject IDENTIFIER | none | reject
No processing is applied to custom properties.
Values that are of the privileged type string type are unwrapped and allowed to reach a builtin attribute alias.
Values that are of other type string types are unwrapped before being filtered.
Rejected values are replaced with an innocuous value.
There are two main failure modes:
myElement.href = ?javascript:myVeryOwnFunction()?
We can check for false negatives by writing custom elements
<dom-module id=?xss-me?>
<template>
<a href="{{myhref}}">
that use the relevant properties, and instantiating them with variables bound to
known payloads like { ?myhref?: ?javascript:shouldNotBeCalled()? }
.
We will also write regression tests for polygerrit that programmatically creates an author, changelist, and review comment with common payloads, and uses selenium to view the pages and check for breaches.
We will try to get a handle on the kinds of false positives and their frequency by running polygerrit/app/*_test.sh, and looking for regressions.
Running both an instrumented version, and an uninstrumented version side by side in two browser windows should make changes in behavior apparent.
There are a few minor failure modes:
Gerrit builds via bazel but loads most of its scripts via bower. Polymer resin is available as a bower component.
There are three deployment options.
polymer-resin.html
which is best for closure-friendly polymer apps.standalone/polymer-resin.html
which includes a single JS bundle that
includes pre-compiled JS.standalone/polymer-resin-debug.html
which is like the previous file but
the JS is not obfuscated and it logs to the console whenever a property
value is rejected.To deploy in the custom element example the head changes from
ipt src="webcomponents-lite.js"></script>
k rel="import" href="custom-element.html">
to
ipt src="webcomponents-lite.js"></script>
k rel="import" href="polymer-resin/polymer-resin.html">
ipt>
his step is essential to the security of this project.
rity.polymer_resin.install();
ript>
k rel="import" href="custom-element.html">
or with one of the standalone variants above. The <script>
is required because
it forces the imports above it to be handled before that of
custom-element.html. The exact text is unimportant but it must be non-empty.
Per https://github.com/Polymer/web-component-tester
make sure that you have bower installed and have run bower update
.
Then use the test script.
run_tests.sh
From the project root
run_tests.sh -p -l chrome
causes it to keep the server open. See the log output for the localhost URL to browse to.