Can I ask a browser to not run <script>s within an element?

17,988

Solution 1

YES, you can :-) The answer is: Content Security Policy (CSP).

Most modern browsers support this flag, which tells the browser only to load JavaScript code from a trusted external file and disallow all internal JavaScript code! The only downside is, you can not use any inline JavaScript in your whole page (not only for a single <div>). Although there could be a workaround by dynamically including the div from an external file with a different security policy, but I'm not sure about that.

But if you can change your site to load all JavaScript from external JavaScript files then you can disable inline JavaScript altogether with this header!

Here is a nice tutorial with example: HTML5Rocks Tutorial

If you can configure the server to send this HTTP-Header flag the world will be a better place!

Solution 2

You can block JavaScript loaded by <script>, using beforescriptexecute event:

<script>
  // Run this as early as possible, it isn't retroactive
  window.addEventListener('beforescriptexecute', function(e) {
    var el = e.target;
    while(el = el.parentElement)
      if(el.hasAttribute('data-no-js'))
        return e.preventDefault(); // Block script
  }, true);
</script>

<script>console.log('Allowed. Console is expected to show this');</script>
<div data-no-js>
  <script>console.log('Blocked. Console is expected to NOT show this');</script>
</div>

Note that beforescriptexecute was defined in HTML 5.0 but has been removed in HTML 5.1. Firefox is the only major browser that implemented it.

In case you are inserting an untrusted bunch of HTML in your page, be aware blocking scripts inside that element won't provide more security, because the untrusted HTML can close the sandboxed element, and thus the script will be placed outside and run.

And this won't block things like <img onerror="javascript:alert('foo')" src="//" />.

Solution 3

Interesting question, I don't think it's possible. But even if it is, it sounds like it would be a hack.

If the contents of that div are untrusted, then you need to escape the data on the server side before it is sent in the HTTP response and rendered in the browser.

If you only want to remove <script> tags and allow other html tags, then just strip them out of the content and leave the rest.

Look into XSS prevention.

https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet

Solution 4

JavaScript is executed "inline", i.e. in the order in which it appears in the DOM (if that wasn't the case, you could never be sure that some variable defined in a different script was visible when you used it for the first time).

So that means in theory you could have a script at the beginning of the page (i.e. first <script> element) which looks through the DOM and removed all <script> elements and event handlers inside of your <div>.

But the reality is more complex: DOM and script loading happens asynchronously. This means that the browser only guarantees that a script can see the part of the DOM which is before it (i.e. the header so far in our example). There are no guarantees for anything beyond (this is related to document.write()). So you might see the next script tag or maybe, you don't.

You could latch to the onload event of the document - which would make sure you got the whole DOM - but at that time, malicious code could have already executed. Things get worse when other scripts manipulate the DOM, adding scripts there. So you would have to check for every change of the DOM, too.

So @cowls solution (filtering on the server) is the only solution which can be made to work in all situations.

Solution 5

I've got a theory:

  • Wrap the specific part of the document inside a noscript tag.
  • Use DOM functions to discard all script tags inside the noscript tag then unwrap its contents.

Proof of concept example:

window.onload = function() {
    var noscripts = /* _live_ list */ document.getElementsByTagName("noscript"),
        memorydiv = document.createElement("div"),
        scripts = /* _live_ list */ memorydiv.getElementsByTagName("script"),
        i,
        j;
    for (i = noscripts.length - 1; i >= 0; --i) {
        memorydiv.innerHTML = noscripts[i].textContent || noscripts[i].innerText;
        for (j = scripts.length - 1; j >= 0; --j) {
            memorydiv.removeChild(scripts[j]);
        }
        while (memorydiv.firstChild) {
            noscripts[i].parentNode.insertBefore(memorydiv.firstChild, noscripts[i]);
        }
        noscripts[i].parentNode.removeChild(noscripts[i]);
    }
};
body { font: medium/1.5 monospace; }
p, h1 { margin: 0; }
<h1>Sample Content</h1>
<p>1. This paragraph is embedded in HTML</p>
<script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
<p>3. This paragraph is embedded in HTML</p>
<h1>Sample Content in No-JavaScript Zone</h1>
<noscript>
    <p>1. This paragraph is embedded in HTML</p>
    <script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
    <p>3. This paragraph is embedded in HTML</p>
</noscript>
<noscript>
    <p>1. This paragraph is embedded in HTML</p>
    <script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
    <p>3. This paragraph is embedded in HTML</p>
</noscript>
Share:
17,988

Related videos on Youtube

Seppo Erviälä
Author by

Seppo Erviälä

Updated on July 19, 2022

Comments

  • Seppo Erviälä
    Seppo Erviälä almost 2 years

    Is it possible to tell browsers to not run JavaScript from specific parts of an HTML document?

    Like:

    <div script="false"> ...
    

    It could be useful as an additional security feature. All the scripts I want are loaded in a specific part of the document. There should be no scripts in other parts of the document and if there are they should not be run.

    • freefaller
      freefaller over 9 years
      Not that I'm aware of. This is a comment rather than an answer, because I can't say 100%
    • chiastic-security
      chiastic-security over 9 years
      Are you in control of the headers? If so... can you put some JavaScript that loads at the beginning, and traverses the DOM to kill off any other scripts it finds before they load? This won't deal with dynamically loaded scripts, of course, but once the only scripts running are your own, you ought to be in control of what other scripts might get dynamically loaded...
    • cowls
      cowls over 9 years
      @chiastic-security You can only traverse the DOM after the DOM is loaded. By which point any JS in that block would have executed.
    • Christoph
      Christoph over 9 years
      The closest I can think of is the content security policy, where you can restrict scripts by their origin (perhaps that is what you want). E.g. by specifying script-src:"self" you allow only scripts from your domain to run in the page. If you are interested, read this article from Mike West about CSP.
    • chiastic-security
      chiastic-security over 9 years
      @Christoph Ah, interesting. Is it possible to alter that dynamically? If so, and OP needs some external scripts to run, perhaps it's possible to combine this with what I was suggesting. Start with script-src:"self", until the full DOM is loaded; then (with a script from the same domain) walk the DOM and remove any extra scripts that shouldn't be there; then relax the script-src restriction. But I'm not sure how this would deal with embedded scripts: presumably they will be seen as coming from the right domain, and they'll get executed anyway.
    • Christoph
      Christoph over 9 years
      this is indeed that case because it is assumed that you have control over your own code, right? If it is about some internal conventions (e.g. the company requiring it), I would check the page on the server side before delivering it.
    • Dagg Nabbit
      Dagg Nabbit over 9 years
      @chiastic-security how do you plan to relax a restriction specified in a header after the headers have already been sent? Anyway, since CSP disables inline scripts by default, and remote scripts won't load unless whitelisted, why would you need to combine it with anything else? CSP is probably the OP's best bet; I hope someone leaves a CSP answer.
    • Seppo Erviälä
      Seppo Erviälä over 9 years
      @Christoph CSP seems like the solution I'm looking for. Forbidding resources from outside domains, inline-scripting and use of eval seem ideal for excluding unwanted scripts that might be snuck in.
    • Damien_The_Unbeliever
      Damien_The_Unbeliever over 9 years
      If you're concerned about someone injecting arbitrary blocks into your HTML, how is your proposed solution going to prevent them injecting a </div> to close this DOM element and then starting a new <div> that will be a sibling of the one where scripts aren't running?
    • Seppo Erviälä
      Seppo Erviälä over 9 years
      @Damien_The_Unbeliever It is not going to prevent that. After reading the comments it seems that CSP is the answer I was looking for.
    • superluminary
      superluminary over 9 years
      Are you worried about XSS? To my knowledge the only defence against that is serverside.
    • Ajedi32
      Ajedi32 over 9 years
      Well, there's the <noscript> tag... :trollface:
    • 11684
      11684 over 9 years
      @Ajedi32 That may be a joke, but it would circumvent the problem stated by Aaron Digullis. If you'd wrap all user-definable content in noscript tags you could then later check the contents of those tags and extract the content while leaving scripts inside.
    • Ajedi32
      Ajedi32 over 9 years
      @11684 I don't see how. <noscript> just defines content which is only displayed if your browser doesn't support JavaScript (or has it disabled). =/
    • 11684
      11684 over 9 years
      @Ajedi32 And prevents the scripts from being executed! See this question+answer(s): stackoverflow.com/questions/25359600/…
    • Ajedi32
      Ajedi32 over 9 years
      @11684 Never mind, I didn't see your edit. If you're not escaping the user-definable content though it doesn't matter where you put it in the document. E.g. The user could just write </noscript><script> run_code(); </script><noscript>.
    • 11684
      11684 over 9 years
      @Ajedi32 =:/... Well, what if you'd just do content.replace("noscript", "");? But by then content.escapeHtmlSpecialChars(); would be easier...
    • Ajedi32
      Ajedi32 over 9 years
      @11684 That's also super-easy to circumvent. </nnoscriptoscript><script> run_code(); </script><nnoscriptoscript>. Point is, don't bother trying to implement this sort of XSS protection yourself. The only effective solution is to use proper escaping of user input, and then whitelist the tags you want to allow. (I won't go into detail, this topic has already been covered quite thoroughly elsewhere.)
  • BrianH
    BrianH over 9 years
    +1 That's actually really cool, I had no idea that existed! (one note, your wiki link is to the German version directly) Here's the rundown on browser support: caniuse.com/#feat=contentsecuritypolicy
  • Ajedi32
    Ajedi32 over 9 years
    Note that even if you do this, allowing unescaped user input on a page is still a bad idea. That would basically allow the user to change the page to look however they want. Even with all Content Security Policy (CSP) settings set to maximum (disallowing inline scripts, styles, etc), the user could still perform a cross-site request forgery (CSRF) attack using image srcs for GET requests, or tricking the user into clicking a form submit button for POST requests.
  • Falco
    Falco over 9 years
    @Ajedi32 Of course you should always sanitize user inputs. But CSP can even set policies for GET requests like images or css, it will not only block them but even inform your server about it!
  • Ajedi32
    Ajedi32 over 9 years
    @Falco I read the docs, and my understanding was that you can only restrict those requests to a given domain, not a specific set of subpages on the domain. Presumably the domain you set would be the same one as your site, meaning that you would still be open to CSRF attacks.
  • Ajedi32
    Ajedi32 over 9 years
    @Falco Granted though, if your site is vulnerable to CSRF attacks that's a problem whether or not you're allowing users to put unescaped HTML on your site. Although, if your current method for preventing CSRF attacks involves checking the referrer header, that method wouldn't work against attacks originating from your own site.
  • Falco
    Falco over 9 years
    @Ajedi32 you are right, if you provide the unescaped user-generated content on a page with the same subdomain as your relevant requests, CSP can not protect against it. I would contain the user-generated content in a sandboxed subdomain-zone and not allow any relevant GET/POST requests from there... AND of course santizie the input ;-)
  • Salman A
    Salman A over 9 years
    The fiddle does not seem to work as expected. I should not be able to see the "blocked" part, right?
  • Ajedi32
    Ajedi32 over 9 years
    @Falco Yeah, that's basically what StackExchange did with the new Stack Snippets feature: blog.stackoverflow.com/2014/09/… If you properly sanitize the input though, a seperate domain isn't necessary.
  • Oriol
    Oriol over 9 years
    @SalmanA Exactly. Probably, your browser doesn't support beforescriptexecute event. It works on Firefox.
  • cowls
    cowls over 9 years
    If the content in the div is untrusted, which i guess it is given the question. They could just close the <noscript> tag and then inject what they like.
  • Salman A
    Salman A over 9 years
    Yes, the proper solution is to fix the problem on the server side. What I am doing via JavaScript should better be done on the server side.
  • AlbertEngelB
    AlbertEngelB over 9 years
    Just for anyone else coming here, CSP is very much still being worked on. Browser support comes and goes depending on updates. I've had Chrome break CSP completely at least twice until I went the report-only route.
  • Mark Hurd
    Mark Hurd almost 8 years
    Probably because it's not working in Chrome, with the snippet as provided, though I see you've only just converted it to a snippet :-)
  • Matt Pennington
    Matt Pennington over 7 years
    beforescriptexecute looks like it is not supported and will not be supported by most major browsers. developer.mozilla.org/en-US/docs/Web/Events/beforescriptexec‌​ute