From 891e76d9b5e1800bfbe9aaae1fa822940260ece4 Mon Sep 17 00:00:00 2001 From: mkwst <mkwst@chromium.org> Date: Tue, 16 May 2017 14:18:45 -0700 Subject: [PATCH] Move `<script nonce>` hiding to `Element`. We're evaluating a different approach to hiding the `nonce` content attribute, moving the behavior change up to `HTMLElement` and `SVGElement` rather than placing it on `{HTML,SVG}{Script,Style}Element`. This patch adds `nonce` to `ElementRareData` in order to support that approach, and wires up a new `NoncedElement` interface to the new properties. Still behind a flag while we're working out details. Intent to Implement and Ship: https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/wu_fMIYkyaQ/85j16Cg6BAAJ BUG=680419 Review-Url: https://codereview.chromium.org/2801243002 Cr-Commit-Position: refs/heads/master@{#472215} --- .../script-nonces-hidden-meta.html | 116 +++++++++++++++++ .../_unapproved/script-nonces-hidden.html | 114 ++++++++++++++++ .../script-nonces-hidden.html.headers | 1 + .../svgscript-nonces-hidden-meta.html | 122 ++++++++++++++++++ .../_unapproved/svgscript-nonces-hidden.html | 122 ++++++++++++++++++ .../svgscript-nonces-hidden.html.headers | 1 + 6 files changed, 476 insertions(+) create mode 100644 content-security-policy/_unapproved/script-nonces-hidden-meta.html create mode 100644 content-security-policy/_unapproved/script-nonces-hidden.html create mode 100644 content-security-policy/_unapproved/script-nonces-hidden.html.headers create mode 100644 content-security-policy/_unapproved/svgscript-nonces-hidden-meta.html create mode 100644 content-security-policy/_unapproved/svgscript-nonces-hidden.html create mode 100644 content-security-policy/_unapproved/svgscript-nonces-hidden.html.headers diff --git a/content-security-policy/_unapproved/script-nonces-hidden-meta.html b/content-security-policy/_unapproved/script-nonces-hidden-meta.html new file mode 100644 index 00000000000000..812e50dfb9147b --- /dev/null +++ b/content-security-policy/_unapproved/script-nonces-hidden-meta.html @@ -0,0 +1,116 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<meta http-equiv="content-security-policy" content="script-src 'nonce-abc'; img-src 'none'"> + +<body> +<!-- Basics --> +<script nonce="abc" id="testScript"> + document.currentScript.setAttribute('executed', 'yay'); +</script> + +<script nonce="abc"> + var script = document.querySelector('#testScript'); + + test(t => { + // Query Selector + assert_equals(document.querySelector('body [nonce]'), script); + assert_equals(document.querySelector('body [nonce=""]'), null); + assert_equals(document.querySelector('body [nonce=abc]'), script); + + assert_equals(script.getAttribute('nonce'), 'abc'); + assert_equals(script.nonce, 'abc'); + }, "Reading 'nonce' content attribute and IDL attribute."); + + // Clone node. + test(t => { + script.setAttribute('executed', 'boo'); + var s2 = script.cloneNode(); + assert_equals(s2.nonce, 'abc', 'IDL attribute'); + assert_equals(s2.getAttribute('nonce'), 'abc'); + }, "Cloned node retains nonce."); + + async_test(t => { + var s2 = script.cloneNode(); + document.head.appendChild(s2); + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(s2.nonce, 'abc'); + assert_equals(s2.getAttribute('nonce'), 'abc'); + + // The cloned script won't execute, as its 'already started' flag is set. + assert_equals(s2.getAttribute('executed'), 'boo'); + })); + }, "Cloned node retains nonce when inserted."); + + // Set the content attribute to 'foo' + test(t => { + script.setAttribute('nonce', 'foo'); + assert_equals(script.getAttribute('nonce'), 'foo'); + assert_equals(script.nonce, 'abc'); + }, "Writing 'nonce' content attribute."); + + // Set the IDL attribute to 'bar' + test(t => { + script.nonce = 'bar'; + assert_equals(script.nonce, 'bar'); + assert_equals(script.getAttribute('nonce'), 'foo'); + }, "Writing 'nonce' IDL attribute."); + + // Fragment parser. + var documentWriteTest = async_test("Document-written script executes."); + document.write(`<script nonce='abc'> + documentWriteTest.done(); + test(t => { + var script = document.currentScript; + assert_equals(script.getAttribute('nonce'), 'abc'); + assert_equals(script.nonce, 'abc'); + }, "Document-written script's nonce value."); + </scr` + `ipt>`); + + // Create node. + async_test(t => { + var s = document.createElement('script'); + s.innerText = script.innerText; + s.nonce = 'abc'; + document.head.appendChild(s); + + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(s.nonce, 'abc'); + assert_equals(s.getAttribute('nonce'), null); + assert_equals(s.getAttribute('executed'), 'yay'); + })); + }, "createElement.nonce."); + + // Create node. + async_test(t => { + var s = document.createElement('script'); + s.innerText = script.innerText; + s.setAttribute('nonce', 'abc'); + assert_equals(s.getAttribute('nonce'), 'abc', "Pre-insertion content"); + assert_equals(s.nonce, '', "Pre-insertion IDL"); + document.head.appendChild(s); + + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(s.nonce, 'abc', "Post-insertion IDL"); + assert_equals(s.getAttribute('nonce'), 'abc', "Post-insertion content"); + assert_equals(s.getAttribute('executed'), 'yay'); + })); + }, "createElement.setAttribute."); +</script> + +<!-- CSS Leakage --> +<style> + #cssTest { display: block; } + #cssTest[nonce=abc] { background: url(/security/resources/abe.png); } +</style> +<script nonce="abc" id="cssTest"> + async_test(t => { + requestAnimationFrame(t.step_func_done(_ => { + var script = document.querySelector('#cssTest'); + var style = getComputedStyle(script); + assert_equals(style['display'], 'block'); + assert_equals(style['background-image'], "url(\"http://web-platform.test:8001/security/resources/abe.png\")"); + })); + }, "Nonces leak via CSS side-channels."); +</script> diff --git a/content-security-policy/_unapproved/script-nonces-hidden.html b/content-security-policy/_unapproved/script-nonces-hidden.html new file mode 100644 index 00000000000000..41636331bbf82b --- /dev/null +++ b/content-security-policy/_unapproved/script-nonces-hidden.html @@ -0,0 +1,114 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js" nonce="abc"></script> +<script src="/resources/testharnessreport.js" nonce="abc"></script> + +<!-- `Content-Security-Policy: script-src 'nonce-abc'; img-src 'none'` delivered via headers --> + +<body> +<!-- Basics --> +<script nonce="abc" id="testScript"> + document.currentScript.setAttribute('executed', 'yay'); +</script> + +<script nonce="abc"> + var script = document.querySelector('#testScript'); + + test(t => { + // Query Selector + assert_equals(document.querySelector('body [nonce]'), script); + assert_equals(document.querySelector('body [nonce=""]'), script); + assert_equals(document.querySelector('body [nonce=abc]'), null); + + assert_equals(script.getAttribute('nonce'), ''); + assert_equals(script.nonce, 'abc'); + }, "Reading 'nonce' content attribute and IDL attribute."); + + // Clone node. + test(t => { + script.setAttribute('executed', 'boo'); + var s2 = script.cloneNode(); + assert_equals(s2.nonce, 'abc', 'IDL attribute'); + assert_equals(s2.getAttribute('nonce'), ''); + }, "Cloned node retains nonce."); + + async_test(t => { + var s2 = script.cloneNode(); + document.head.appendChild(s2); + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(s2.nonce, 'abc'); + assert_equals(s2.getAttribute('nonce'), ''); + + // The cloned script won't execute, as its 'already started' flag is set. + assert_equals(s2.getAttribute('executed'), 'boo'); + })); + }, "Cloned node retains nonce when inserted."); + + // Set the content attribute to 'foo' + test(t => { + script.setAttribute('nonce', 'foo'); + assert_equals(script.getAttribute('nonce'), 'foo'); + assert_equals(script.nonce, 'abc'); + }, "Writing 'nonce' content attribute."); + + // Set the IDL attribute to 'bar' + test(t => { + script.nonce = 'bar'; + assert_equals(script.nonce, 'bar'); + assert_equals(script.getAttribute('nonce'), 'foo'); + }, "Writing 'nonce' IDL attribute."); + + // Fragment parser. + var documentWriteTest = async_test("Document-written script executes."); + document.write(`<script nonce='abc'> + documentWriteTest.done(); + test(t => { + var script = document.currentScript; + assert_equals(script.getAttribute('nonce'), ''); + assert_equals(script.nonce, 'abc'); + }, "Document-written script's nonce value."); + </scr` + `ipt>`); + + // Create node. + async_test(t => { + var s = document.createElement('script'); + s.innerText = script.innerText; + s.nonce = 'abc'; + document.head.appendChild(s); + + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(s.nonce, 'abc'); + assert_equals(s.getAttribute('nonce'), null); + })); + }, "createElement.nonce."); + + // Create node. + async_test(t => { + var s = document.createElement('script'); + s.innerText = script.innerText; + s.setAttribute('nonce', 'abc'); + assert_equals(s.getAttribute('nonce'), 'abc', "Pre-insertion content"); + assert_equals(s.nonce, '', "Pre-insertion IDL"); + document.head.appendChild(s); + + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(s.nonce, 'abc', "Post-insertion IDL"); + assert_equals(s.getAttribute('nonce'), '', "Post-insertion content"); + })); + }, "createElement.setAttribute."); +</script> + +<!-- CSS Leakage --> +<style> + #cssTest { display: block; } + #cssTest[nonce=abc] { background: url(/security/resources/abe.png); } +</style> +<script nonce="abc" id="cssTest"> + async_test(t => { + requestAnimationFrame(t.step_func_done(_ => { + var script = document.querySelector('#cssTest'); + var style = getComputedStyle(script); + assert_equals(style['display'], 'block'); + assert_equals(style['background-image'], 'none'); + })); + }, "Nonces don't leak via CSS side-channels."); +</script> diff --git a/content-security-policy/_unapproved/script-nonces-hidden.html.headers b/content-security-policy/_unapproved/script-nonces-hidden.html.headers new file mode 100644 index 00000000000000..ad8d0b54f31d6d --- /dev/null +++ b/content-security-policy/_unapproved/script-nonces-hidden.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: script-src 'nonce-abc'; img-src 'none' diff --git a/content-security-policy/_unapproved/svgscript-nonces-hidden-meta.html b/content-security-policy/_unapproved/svgscript-nonces-hidden-meta.html new file mode 100644 index 00000000000000..46da267a94f01f --- /dev/null +++ b/content-security-policy/_unapproved/svgscript-nonces-hidden-meta.html @@ -0,0 +1,122 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<meta http-equiv="content-security-policy" content="script-src 'nonce-abc'; img-src 'none'"> + +<body> +<!-- Basics --> +<svg xmlns="http://www.w3.org/2000/svg"> + <script nonce="abc" id="testScript"> + document.currentScript.setAttribute('executed', 'yay'); + </script> +</svg> + +<script nonce="abc"> + var script = document.querySelector('#testScript'); + + test(t => { + // Query Selector + assert_equals(document.querySelector('[nonce]'), script); + assert_equals(document.querySelector('[nonce=""]'), null); + assert_equals(document.querySelector('[nonce=abc]'), script); + + assert_equals(script.getAttribute('nonce'), 'abc'); + assert_equals(script.nonce, 'abc'); + }, "Reading 'nonce' content attribute and IDL attribute."); + + // Clone node. + test(t => { + script.setAttribute('executed', 'boo'); + var s2 = script.cloneNode(); + assert_equals(s2.nonce, 'abc', 'IDL attribute'); + assert_equals(s2.getAttribute('nonce'), 'abc'); + }, "Cloned node retains nonce."); + + async_test(t => { + var s2 = script.cloneNode(); + document.head.appendChild(s2); + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(s2.nonce, 'abc'); + assert_equals(s2.getAttribute('nonce'), 'abc'); + + // The cloned script won't execute, as its 'already started' flag is set. + assert_equals(s2.getAttribute('executed'), 'boo'); + })); + }, "Cloned node retains nonce when inserted."); + + // Set the content attribute to 'foo' + test(t => { + script.setAttribute('nonce', 'foo'); + assert_equals(script.getAttribute('nonce'), 'foo'); + assert_equals(script.nonce, 'abc'); + }, "Writing 'nonce' content attribute."); + + // Set the IDL attribute to 'bar' + test(t => { + script.nonce = 'bar'; + assert_equals(script.nonce, 'bar'); + assert_equals(script.getAttribute('nonce'), 'foo'); + }, "Writing 'nonce' IDL attribute."); + + // Fragment parser. + var documentWriteTest = async_test("Document-written script executes."); + document.write(`<svg xmlns="http://www.w3.org/2000/svg"><script nonce='abc'> + documentWriteTest.done(); + test(t => { + var script = document.currentScript; + assert_equals(script.getAttribute('nonce'), 'abc'); + assert_equals(script.nonce, 'abc'); + }, "Document-written script's nonce value."); + </scr` + `ipt></svg>`); + + // Create node. + async_test(t => { + var s = document.createElement('svg'); + var innerScript = document.createElement('innerScript'); + innerScript.innerText = script.innerText; + innerScript.nonce = 'abc'; + s.appendChild(innerScript); + document.body.appendChild(s); + + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(innerScript.nonce, 'abc'); + assert_equals(innerScript.getAttribute('nonce'), null, 'innerScript.getAttribute nonce'); + })); + }, "createElement.nonce."); + + // Create node. + async_test(t => { + var s = document.createElement('svg'); + var innerScript = document.createElement('script'); + innerScript.innerText = script.innerText; + innerScript.setAttribute('nonce', 'abc'); + assert_equals(innerScript.getAttribute('nonce'), 'abc', "Pre-insertion content"); + assert_equals(innerScript.nonce, '', "Pre-insertion IDL"); + s.appendChild(innerScript); + document.body.appendChild(s); + + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(innerScript.nonce, 'abc', "Post-insertion IDL"); + assert_equals(innerScript.getAttribute('nonce'), 'abc', "Post-insertion content"); + })); + }, "createElement.setAttribute."); +</script> + +<!-- CSS Leakage --> +<style> + #cssTest { display: block; } + #cssTest[nonce=abc] { background: url(/security/resources/abe.png); } +</style> +<svg xmlns="http://www.w3.org/2000/svg"> + <script nonce="abc" id="cssTest"> + async_test(t => { + requestAnimationFrame(t.step_func_done(_ => { + var script = document.querySelector('#cssTest'); + var style = getComputedStyle(script); + assert_equals(style['display'], 'block'); + assert_equals(style['background-image'], "url(\"http://web-platform.test:8001/security/resources/abe.png\")"); + })); + }, "Nonces don't leak via CSS side-channels."); + </script> +</svg> diff --git a/content-security-policy/_unapproved/svgscript-nonces-hidden.html b/content-security-policy/_unapproved/svgscript-nonces-hidden.html new file mode 100644 index 00000000000000..ddba1a9e8955a7 --- /dev/null +++ b/content-security-policy/_unapproved/svgscript-nonces-hidden.html @@ -0,0 +1,122 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js" nonce="abc"></script> +<script src="/resources/testharnessreport.js" nonce="abc"></script> + +<!-- `Content-Security-Policy: script-src 'nonce-abc'; img-src 'none'` delivered via headers --> + +<body> +<!-- Basics --> +<svg xmlns="http://www.w3.org/2000/svg"> + <script nonce="abc" id="testScript"> + document.currentScript.setAttribute('executed', 'yay'); + </script> +</svg> + +<script nonce="abc"> + var script = document.querySelector('#testScript'); + + test(t => { + // Query Selector + assert_equals(document.querySelector('body [nonce]'), script); + assert_equals(document.querySelector('body [nonce=""]'), script); + assert_equals(document.querySelector('body [nonce=abc]'), null); + + assert_equals(script.getAttribute('nonce'), ''); + assert_equals(script.nonce, 'abc'); + }, "Reading 'nonce' content attribute and IDL attribute."); + + // Clone node. + test(t => { + script.setAttribute('executed', 'boo'); + var s2 = script.cloneNode(); + assert_equals(s2.nonce, 'abc', 'IDL attribute'); + assert_equals(s2.getAttribute('nonce'), ''); + }, "Cloned node retains nonce."); + + async_test(t => { + var s2 = script.cloneNode(); + document.head.appendChild(s2); + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(s2.nonce, 'abc'); + assert_equals(s2.getAttribute('nonce'), ''); + + // The cloned script won't execute, as its 'already started' flag is set. + assert_equals(s2.getAttribute('executed'), 'boo'); + })); + }, "Cloned node retains nonce when inserted."); + + // Set the content attribute to 'foo' + test(t => { + script.setAttribute('nonce', 'foo'); + assert_equals(script.getAttribute('nonce'), 'foo'); + assert_equals(script.nonce, 'abc'); + }, "Writing 'nonce' content attribute."); + + // Set the IDL attribute to 'bar' + test(t => { + script.nonce = 'bar'; + assert_equals(script.nonce, 'bar'); + assert_equals(script.getAttribute('nonce'), 'foo'); + }, "Writing 'nonce' IDL attribute."); + + // Fragment parser. + var documentWriteTest = async_test("Document-written script executes."); + document.write(`<svg xmlns="http://www.w3.org/2000/svg"><script nonce='abc'> + documentWriteTest.done(); + test(t => { + var script = document.currentScript; + assert_equals(script.getAttribute('nonce'), ''); + assert_equals(script.nonce, 'abc'); + }, "Document-written script's nonce value."); + </scr` + `ipt></svg>`); + + // Create node. + async_test(t => { + var s = document.createElement('svg'); + var innerScript = document.createElement('script'); + innerScript.innerText = script.innerText; + innerScript.nonce = 'abc'; + s.appendChild(innerScript); + document.body.appendChild(s); + + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(innerScript.nonce, 'abc'); + assert_equals(innerScript.getAttribute('nonce'), null); + })); + }, "createElement.nonce."); + + // Create node. + async_test(t => { + var s = document.createElement('svg'); + var innerScript = document.createElement('script'); + innerScript.innerText = script.innerText; + innerScript.setAttribute('nonce', 'abc'); + assert_equals(innerScript.getAttribute('nonce'), 'abc', "Pre-insertion content"); + assert_equals(innerScript.nonce, '', "Pre-insertion IDL"); + s.appendChild(innerScript); + document.body.appendChild(s); + + window.addEventListener('load', t.step_func_done(_ => { + assert_equals(innerScript.nonce, 'abc', "Post-insertion IDL"); + assert_equals(innerScript.getAttribute('nonce'), '', "Post-insertion content"); + })); + }, "createElement.setAttribute."); +</script> + +<!-- CSS Leakage --> +<style> + #cssTest { display: block; } + #cssTest[nonce=abc] { background: url(/security/resources/abe.png); } +</style> +<svg xmlns="http://www.w3.org/2000/svg"> + <script nonce="abc" id="cssTest"> + async_test(t => { + requestAnimationFrame(t.step_func_done(_ => { + var script = document.querySelector('#cssTest'); + var style = getComputedStyle(script); + assert_equals(style['display'], 'block'); + assert_equals(style['background-image'], 'none'); + })); + }, "Nonces don't leak via CSS side-channels."); + </script> +</svg> diff --git a/content-security-policy/_unapproved/svgscript-nonces-hidden.html.headers b/content-security-policy/_unapproved/svgscript-nonces-hidden.html.headers new file mode 100644 index 00000000000000..ad8d0b54f31d6d --- /dev/null +++ b/content-security-policy/_unapproved/svgscript-nonces-hidden.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: script-src 'nonce-abc'; img-src 'none'