I'm just a bit confused. I've been going through a presentation slide deck on Content Security Policies. On slide 10, when discussing the 'strict-dynamic'
directive, three examples are given: one good, two bad:
// good
<script nonce="r4nd0m">
var s = document.createElement("script");
s.src = "//example.com/bar.js";
document.body.appendChild(s);
</script>
// bad
<script nonce="r4nd0m">
var s = "<script src=//example.com/bar.js></script>";
document.write(s);
// -OR-
document.body.innerHTML = s;
</script>
Google also advises to make similar changes on their CSP page ("Refactor calls to JS APIs incompatible with CSP").
I understand that the good example loads async while the bad one doesn't (unless async defer
is added). But...
If I wanted to load an evil script, wouldn't both ways get me there?
Is the issue that the "bad" way doesn't propagate the nonce (automatically)? Or is it about the "bad" way being a bit like a call to eval() in that you can do pretty much anything with it (although, in HTML 5, a script wouldn't execute unless attached to an event handler, apparently)? Is the assumption that the "bad" way likely uses parser-inserted user input and that's why it's bad (that's what I got from the presentation video I found later)?
Why is one call permissible while the other isn't (when, in the example given, they seem to achieve the same thing - loading an external script)?