Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/mixins/property-effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,9 @@ function applyBindingValue(inst, node, binding, part, value) {
value = computeBindingValue(node, value, binding, part);
if (sanitizeDOMValue) {
value = sanitizeDOMValue(value, binding.target, binding.kind, node);
} else if ((binding.target === 'inner-h-t-m-l' || binding.target === 'innerHTML') && typeof value === 'string') {
value = value.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
value = value.replace(/on\w+\s*=/gi, 'sanitized-');
}
if (binding.kind == 'attribute') {
// Attribute binding
Expand Down
5 changes: 5 additions & 0 deletions lib/utils/resolve-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ let resolveDoc;
*/
export function resolveUrl(url, baseURI) {
if (url && ABS_URL.test(url)) {
const protocol = (url.split(':')[0] || '').toLowerCase();
const unsafeProtocols = ['javascript', 'data', 'vbscript'];
if (unsafeProtocols.includes(protocol)) {
return 'about:blank';
}
return url;
}
if (url === '//') {
Expand Down
103 changes: 103 additions & 0 deletions test/unit/security.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<meta charset="utf-8">
<script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
<script src="wct-browser-config.js"></script>
<script src="../../node_modules/wct-browser-legacy/browser.js"></script>
</head>
<body>

<dom-module id="xss-element">
<template>
<div id="target" inner-h-t-m-l="[[payload]]"></div>
</template>
<script type="module">
import { PolymerElement } from '../../polymer-element.js';
class XssElement extends PolymerElement {
static get is() { return 'xss-element'; }
static get properties() {
return {
payload: String
};
}
}
customElements.define(XssElement.is, XssElement);
</script>
</dom-module>

<script type="module">
import { resolveUrl } from '../../lib/utils/resolve-url.js';

suite('Security: XSS Vulnerability Tests', function() {

suite('resolveUrl protocol bypass', function() {

test('resolveUrl should not allow javascript: protocol', function() {
const payload = 'javascript:alert(1)';
const resolved = resolveUrl(payload);
// Currently, it returns it as-is because it matches ABS_URL regex
// Fix should return 'about:blank' or sanitize it.
assert.notEqual(resolved, payload, 'Should not return javascript: protocol as-is');
});

test('resolveUrl should not allow data: protocol', function() {
const payload = 'data:text/html,<script>alert(1)</' + 'script>';
const resolved = resolveUrl(payload);
assert.notEqual(resolved, payload, 'Should not return data: protocol as-is');
});

test('resolveUrl should allow http: protocol', function() {
const payload = 'http://example.com';
const resolved = resolveUrl(payload);
assert.equal(resolved, payload);
});

test('resolveUrl should allow https: protocol', function() {
const payload = 'https://example.com';
const resolved = resolveUrl(payload);
assert.equal(resolved, payload);
});

});

suite('inner-h-t-m-l binding injection', function() {
let el;

setup(function() {
el = document.createElement('xss-element');
document.body.appendChild(el);
});

teardown(function() {
document.body.removeChild(el);
});

test('inner-h-t-m-l binding should be sanitized by default', function() {
const payload = '<img src=x onerror=window.__xss_triggered=true>';
window.__xss_triggered = false;

el.payload = payload;

// We check if the payload was inserted as-is
// In a secure system, the onerror or the whole tag should be sanitized
const innerHTML = el.$.target.innerHTML;
assert.notInclude(innerHTML, 'onerror', 'Should not include event handlers in inner-h-t-m-l');
assert.isFalse(window.__xss_triggered, 'XSS should not be triggered');
});

});

});
</script>
</body>
</html>