Skip to content

Strict-Dynamic CSP doesn't prevent execution of parser inserted scripts via document.createRange().createContextualFragment #708

@andreituicu

Description

@andreituicu

Problem

strict-dynamic Content Security Policy doesn't prevent execution of parser inserted scripts via document.createRange().createContextualFragment.

This behaviour seems unexpected and can be an easy target to XSS and CSP bypass, as a number of developers are using document.createRange().createContextualFragment as an interchangeable alternative to Element.innerHTML, Element.outerHTML, Element.insertAdjacentHTML to transform strings into DOM.
Not only does the script get executed without the CSP, as opposed to the behaviour of the other properties, but it is also executed even if strict-dynamic is present, which came as a surprise, because intuitively it looks like a parser injected script: A string is directly converted into a script, without the step of invoking document.createElement("script") separately.

Steps to reproduce

  1. Create an HTML with content security policy strict-dynamic + nonce.
  2. Create a trusted script with the nonce
  3. In the trusted script, invoke document.createRange().createContextualFragment('<script>alert("executed")</script>')
  4. Add the result of the previous execution to the dom, in the head

(see below example HTML + JS)

Result

The script is executed.

Expected result

The script should not be executed, because it comes from a string that is parsed. Even if the nonce is set I would expect the string to not be executed.

Example HTML + JS

index.html

<html>
  <head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-r4Nd0mmm' 'strict-dynamic'">
    <script nonce="r4Nd0mmm" src="./script.js"></script>
  </head>
  <body>
    <p>Content Security Policy <strong>nonce</strong> + <strong>strict-dynamic</strong> doesn't prevent parsed scripts from <strong>document.createRange().createContextualFragment()</strong></p>
  </body>
</html>

script.js

// Should not be executed because it's parser inserted
document.head.appendChild(
  document.createRange().createContextualFragment('<script>alert("executed")</script>'),
);

// Should also not be executed because it is still parser inserted, even if it has the valid nonce
document.head.appendChild(
  document.createRange().createContextualFragment('<script nonce="r4Nd0mmm">alert("executed with nonce")</script>'),
);

CC @lkrapf

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions