Skip to content

Scripts only in <head> #395

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
craigfrancis opened this issue May 21, 2019 · 11 comments
Closed

Scripts only in <head> #395

craigfrancis opened this issue May 21, 2019 · 11 comments

Comments

@craigfrancis
Copy link

Taking some ideas from #392, and from the old/dead <plaintext> tag...

Considering most XSS exploits are found in the <body> (where most dynamic content is), and because websites can put all of their <script> tags in the <head> (ideally as async or defer), I would like to tell the browser that no <script> tag will be found after the <body> has started.

As an aside, the <body> is optional, so it's started even if the tag does not exist in the document.

@annevk
Copy link
Member

annevk commented May 21, 2019

What about dynamic insertion?

@craigfrancis
Copy link
Author

Similar to how strict-dynamic allows dynamic insertion from scripts that have a valid hash/nonce, a <script> in the <head> should be allowed to do dynamic insertion.

Not that I use dynamic insertion (can I haz options to block all the things plz - where I'll probably be the only person in the world do that).

@annevk
Copy link
Member

annevk commented May 21, 2019

Also, what about injecting <iframe>, event handlers, or equivalent? This doesn't seem really robust to me.

@craigfrancis
Copy link
Author

Similar to 'strict-dynamic', blessed scripts (in the <head>) should be allowed to add any <iframe>, <script>, event handlers, etc - as I'm focusing on the most likely location for an XSS, which is in the <body> (where you're most likely to find an XSS vulnerability).

And while most XSS issues should be covered by CSP as normal; an attacker might be able to create a bypass in the form of repeating a <script> with a white-listed hash, or guess a poorly implemented nonce, or including a bad <script> from a white-listed location (e.g. JSONP)... so this is just seeing if we could provide an extra barrier.

That said, an attacker adding an <iframe> in the <body> could could break this.

e.g. <iframe srcdoc="EVIL" sandbox="allow-same-origin"> introduces its own <head>.

I'm hesitant to suggest same-origin iframes will have to have scripts in their <head> blocked, as while that won't effect me, I suspect it will be something that's confusing, and too limiting.

Any suggestions? if not, it's probably best closing this.

@Malvoz
Copy link

Malvoz commented May 27, 2019

I have a limited understanding of all of this, but to me it looks like this is something that trusted types could restrict. Am I wrong?

@craigfrancis
Copy link
Author

@Malvoz, not really, CSP is about the content that can be loaded; whereas Trusted Types limits the JavaScript methods/APIs that it can used after it has been loaded (e.g. it restricts the use of innerHTML).

@dveditz
Copy link
Member

dveditz commented Jun 3, 2019

XSS in the <head> is a thing (the title tag was a common vector for a while, and DOMXSS still happens there), but assuming your <head> is perfect could you close out your head with a CSP of script-src 'none'; ? Would block dynamic insertion, but is that really a bad thing?

@craigfrancis
Copy link
Author

craigfrancis commented Jun 6, 2019

Good thinking @dveditz,

I can set a normal CSP header, then just after the last valid <script> tag in the <head>, I can add a <meta> tag to add another CSP, which will stop further scripts being included.

If I do have an XSS in the body, it means attackers can't add any <script> tags, e.g.

<?php
  header("Content-Security-Policy: default-src 'none'; script-src https://example.com/js/");
?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <script src="https://example.com/js/responsive.js"></script>
  <meta http-equiv="Content-Security-Policy" content="script-src 'none'" />
  <title>XSS Example</title>
</head>
<body>
  <p>Hi <?= $_GET['name'] ?></p>
</body>
</html>

@craigfrancis
Copy link
Author

This technique doesn't work for MS Edge 17.17134 (and maybe earlier) when using normal "text/html".

The CSP3 spec says "policies in <meta> elements are not applied to content which precedes them", which implies that it should work, but MS Edge doesn't always follow this rule.


Interestingly, this technique works in Edge when using "application/xhtml+xml".

It will also continue to work if you change to "text/html" and press the Refresh button - as Edge will continue to use the XML parser (more info).

And as a second Edge 17 bug, when adding this <meta> tag, which only adds "script-src 'none'", the style sheets aren't used when printing the page.

I will now skip this <meta> tag if the User-Agent header contains "Edge/", as the new Chromium version seems to be fine, and uses "Edg/".

@briansmith
Copy link

@craigfrancis What if you insert the <meta> dynamically with a script, as suggested in #243 (comment)?

@craigfrancis
Copy link
Author

That's interesting @briansmith, adding it dynamically in MS Edge 17, inline scripts that followed it are now blocked - good; but external scripts are still allowed (most of the time) - not good, but at least all the legitimate scripts still work.


When I say "most of the time", if the evil XSS:

  • Re-included a file, that's always allowed.
  • Included a different file, that's still white-listed by the first CSP, then it seems to be a race condition; e.g. on my local machine, with content cached, Edge allowed it most of the time.

So your approach can be used for all browsers (while Edge 17 is wrong, at least it no longer blocks all JavaScript).

But from a purist point of view, it does introduce inline JavaScript (I don't want to put this in an external file, as I want to load all my JavaScript asynchronously, for performance reasons).


Test Script:

<?php
  $nonce = base64_encode(random_bytes(30));
  header('Content-Type: ' . (false ? 'application/xhtml+xml' : 'text/html') . '; charset=UTF-8');
  header("Content-Security-Policy: default-src 'none'; script-src https://example.com/js/ 'nonce-" . $nonce . "'");
?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>

  <title>MS Edge Test</title>

  <!-- Good Scripts -->
  <script src="https://example.com/js/external1.js"></script>
  <script type="text/javascript" nonce="<?= htmlentities($nonce) ?>">
  	console.log('Allowed Inline1');
  </script>

  <script type="text/javascript" nonce="<?= htmlentities($nonce) ?>">
    var meta = document.createElement('meta');
    meta.httpEquiv = 'Content-Security-Policy';
    meta.content = "script-src 'none'";
    document.head.appendChild(meta);
    // document.write('<meta http-equiv="Content-Security-Policy" content="script-src \'none\'" />');
  </script>
  <!-- <meta http-equiv="Content-Security-Policy" content="script-src 'none'" /> -->
  
  <!-- Bad Scripts, from XSS Exploit -->
  <script src="https://example.com/js/external2.js"></script>
  <script type="text/javascript" nonce="<?= htmlentities($nonce) ?>">
  	console.log('Allowed Inline2');
  </script>

</head>
<body>
  <!-- </div> -->
  <p><a href="./">Reload</a></p>
  <p id="output"></p>
</body>
</html>

Results for appendChild:

  • Allowed External1 ✅
  • Allowed Inline1 ✅
  • Allowed External2 ❌
  • CSP14321: Resource violated directive 'script-src 'none'' in meta http-equiv="Content-Security-Policy">: inline script, in https://example.com/js/ at line 21 column 82. Resource will be blocked. ✅

Results for document.write:

  • Allowed External1 ✅
  • Allowed Inline1 ✅
  • Allowed External2 ❌
  • Allowed Inline2 ❌

Results for <meta>:

  • CSP14312: Resource violated directive 'script-src 'none'' in meta http-equiv="Content-Security-Policy">: https://example.com/js/external1.js. Resource will be blocked. ❌
  • CSP14312: Resource violated directive 'script-src 'none'' in meta http-equiv="Content-Security-Policy">: https://example.com/js/external2.js. Resource will be blocked. ✅
  • CSP14321: Resource violated directive 'script-src 'none'' in meta http-equiv="Content-Security-Policy">: inline script, in https://example.com/js/ at line 7 column 82. Resource will be blocked. ❌
  • CSP14321: Resource violated directive 'script-src 'none'' in meta http-equiv="Content-Security-Policy">: inline script, in https://example.com/js/ at line 14 column 82. Resource will be blocked. ✅

In this last one, note how the order is wrong, the external files are listed first.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants