7

In a comment on this question dealing with XSS prevention, @dandavis suggested:

if your script runs first, it's easy to break following scripts

The idea is to provide a fallback that prevents (inline) JavaScript for browsers that do not support CSP.

I know that it is possible to overwrite existing functions, but it's not clear to me how exactly this would work. Is there a trivial solution that 100% prevents any further script execution? If not, are there solutions that work in most contexts or for most browsers (and ways to bypass them for other contexts/browsers)?

tim
  • 29,122
  • 7
  • 96
  • 120
  • if you break your own page, the part that loads unclean user input, you won't get hit by said content. you can also re-direct from JS, which 100% effective. – dandavis Sep 01 '16 at 20:17

3 Answers3

6

The idea is to provide a fallback that prevents (inline) JavaScript for browsers that do not support CSP.

Bear in mind that CSP is not to be used as a first line of defense. You should properly encode per context to prevent XSS in your application. OWSAP has good information on this.

That being said, CSP can be a good fallback to make XSS much less serious, should an XSS vulnerability (i.e. wrong or no encoding somewhere on your page) be found in your application and exploited.

I know that it is possible to overwrite existing functions, but it's not clear to me how exactly this would work.

It is very easy to do. For example, XMLHttpRequest = myFunction, or XMLHttpRequest = null would prevent scripts from using XMLHttpRequest directly.

The problem is that if an attacker may not be using that particular function, and if the attacker knows you are doing this, he could easily use an alternative method. While it might be possible to overwrite all functions, it is not recommended as a reliable solution.

Is there a trivial solution that 100% prevents any further script execution?

If you have a syntax error in your <script>, then nothing in that <script>...</script> will run. Unfortunately, with XSS, the attacker could easily start a new <script> that would work just fine.

Also there are many many ways to inject JavaScript besides <script>, for example <img onload> or onerror.

To my knowledge you cannot reliably stop all following HTML from containing XSS execution.

If not, are there solutions that work in most contexts or for most browsers (and ways to bypass them for other contexts/browsers)?

CSP version 1 browser support is starting to get good, and since CSP was designed as a backup anyway (not a first line of defense) you may be able to stop your search there knowing that browser support will be a shrinking problem.

That being said, I think the idea of overwriting functions could go a long way towards success. It seems like that could work reliably in theory. While this is not recommended, I think it would be an interesting study. I wonder how complex it would be to implement.

700 Software
  • 13,897
  • 3
  • 53
  • 82
  • 2
    XSS attacks are usually targeted attacks, so just overwriting some of the functions probably won't help. It's really an all-or-nothing kind of situation where you have to get all possibly abused functions and properties, or it's all for nothing. If an attacker were able to inject an iframe, they could gain access to untampered functions. – Alexander O'Mara Aug 31 '16 at 23:14
  • 1
    @AlexanderO'Mara: JS itself provides a list of 99% of the things you need to overwrite, and if you do so, there's no way to inject a frame that can talk to the broken runtime... – dandavis Sep 01 '16 at 09:31
  • Not a cross-origin iframe, just a dummy one which does not load any document. All you would need is the `contentWindow` to get a new set of globals. – Alexander O'Mara Sep 01 '16 at 17:32
  • @AlexanderO'Mara: you can't get to a sub `contentWindow` without getting to `document`... – dandavis Sep 01 '16 at 19:09
  • 1
    There may be a clever way for XSS to access a dummy ` – 700 Software Sep 01 '16 at 19:11
5

 Is there a trivial solution that 100% prevents any further script execution?

No, I don't think so. Every script tag runs on it's own, so throwing an error would not stop the next script. alert and company and perhaps syncronous AJAX come to mind to prevent further scripts from running, but that will lock up the whole browser.

If not, are there solutions that work in most contexts or for most browsers (and ways to bypass them for other contexts/browsers)?

Well, you could try nulling out all the global variables by looping over the properties of window. Then in theory the scripts would not have access to the DOM to do anything. In practice, this probably won't work well as not all of these properties are writable. I'm doubtful it would be possible to remove everything of interest.

Other possible hacks might be to try to destroy the document before other scripts can run, perhaps by using document.write somehow. This would be in the land of undefined behavior and dragons.

Code Sample:

Everyone loves a good code sample, so here an example payload I imagine being tough to beat.

<iframe name="iwin"></iframe>
<script>
(function() {
    // Due to the name attribute on the iframe, iwin is now
    // an implicit global for the iframe's contentWindow.
    // It should not have been there before the tag was parsed,
    // and the name could be randomized.

    // For legacy IE, to make globals accessible (0_o).
    iwin.execScript && iwin.execScript();

    // PWND, we have new globals.
    var XMLHttpRequest = iwin.XMLHttpRequest;
})();
</script>
Alexander O'Mara
  • 8,794
  • 6
  • 34
  • 38
  • almost all global properties are writable except for a few benign ones... – dandavis Sep 01 '16 at 09:32
  • @dandavis `location` isn't, and things like `document.body.innerHTML` can only be prevented via defining new getters, assuming the browser is new enough; IE7- don't have them, and I'm not sure IE8 has a good enough implementation to prevent bypassing. – Alexander O'Mara Sep 01 '16 at 18:22
  • `location` is writable, but like `name`, not how one expects it to be; it's magical to be compat with netscape. `document` props and sub-props are often `host objects`, which may or may not be alterable, but they aren't "global properties" anyway, and we can stop `window.document` – dandavis Sep 01 '16 at 19:07
  • @dandavis How can you stop `window.document`? – Alexander O'Mara Sep 01 '16 at 19:12
  • @dandavis Not working for me in Firefox, `document` is still working as normal. I also added a code sample which does not need `document`. – Alexander O'Mara Sep 01 '16 at 19:44
  • maybe they started protecting it, i'll look into it, thanks a bunch for the response. – dandavis Sep 01 '16 at 19:50
3

I wrote that comment with jQuery in mind, since it's really easy to accidentally break. Since most complicated client-side code (the kind with vectors) uses jQuery, common ajax and templates that cause vulnerabilities simply won't run without it.

JavaScript's security model is reference-based, which means if you can't reach something, it doesn't exist to the code. There are a couple un-expected refs like constructor.constructor("alert(666)")(), but they are manageable because JS simply doesn't have that many built-ins to offer (compared to php or .net's lib). If you can conceal or destroy the reference, you remove the capability. Doug Crockford talks a lot about this, and in "use strict" mode, it's even more enforceable on a granular level.

  • You would be best-off redirecting to a page without script tags; a non-js or "lofi" version, if available.

  • You can use a 100% sized iframe with a sandbox attrib to re-serve the page without script; something i have a bookmarklet for to use for compat testing (just makes a new iframe, sizes it, and set the .src to location.href). Not all browsers support sandbox, so you might be robbing peter to pay paul if your goal is to curtail some-how overlooked XSS.

  • window.stop() works in some browsers (FF for sure), and it's simple enough to use to stop the page loading in-place.

  • "burn them all"; clobber all the built-in global properties, leaving only the pure logic capabilities of the language behind. The downside is that document isn't modifiable, which is all a clever adversary really needs. There may be an easy way to break document, but it's a huge vector as-is.

dandavis
  • 2,693
  • 10
  • 16