-
Notifications
You must be signed in to change notification settings - Fork 84
Description
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
- Create an HTML with content security policy strict-dynamic + nonce.
- Create a trusted script with the nonce
- In the trusted script, invoke document.createRange().createContextualFragment('<script>alert("executed")</script>')
- 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