Bypassing Web Application Firewalls for Cross-Site-Scripting

Web Application Firewalls can make your life much harder when using automated tools. But you can bypass a lot of firewalls when exploiting XSS vulnerabilities by analyzing them manually.

Bypassing Web Application Firewalls for Cross-Site-Scripting

Web Application Firewalls (WAFs) are the point at which automated scanners and tools might start struggling. But completely relying on a WAF is dangerous. Depending on the configuration, detection rules/patterns and the security level, bypassing them just takes some manual analysis. I'll show you what the WAF systems of a very big and another mid-sized company are looking for and how I used the information I gathered by analyzing them to craft an example XSS payload to send the company in a vulnerability disclosure.

Prelude

Browsing through a website searching for Reflected XSS vulnerabilities, I'm looking for GET-parameters to tamper with. Think about a URL similiar to the following:

https://not-evil-corp.com/companies/e-corp-labs?language=de_DE

The language GET-parameter (de_DE) is being used to craft any link on the page to keep the currently selected language.

Example:

<a href="https://not-evil-corp.com/companies/e-coin?language=de_DE">E Coin</a>

First thing I'd try to do is manipulate the parameter so it is absolute nonsense (kessate). Then I'm searching for occurrences of my input in the generated source code. When I see a tag as in my example above, I simply add a double quote.

New parameter: kessate"

Next step is going to the a tag in the source code again to see if the input is not being encoded, so I'm basically escaping the href attribute. If it works, we just found a potential XSS vulnerability.

Trying a simple payload like "><script>alert(document.domain)</script> results in a 403. This is where the actual work starts. My personal goal is to pop up an alert window to demonstrate an XSS vulnerability when sending a report to the company. The reason why I'm doing this is because it is more powerful than just telling them that I can escape the attribute by adding a double quote. Also, I'm challenging myself, and the feeling of finally tricking the WAF manually is a rush.

Step 1: Analyzing the WAF

In order to find a payload that won't trigger the firewall, we need to know what the firewall is looking for. You will get this information by stepping back from your full payload and sending it to the application piece by piece.

Before that, we can try to get rid of the script tag by trying the popular img tag payload:

"><img src=x onerror="alert(document.domain)">

Some systems are just matching very obvious payloads with script tags. But I assume that the above also doesn't work.

Try to send a simple tag (i.e. <x>), that doesn't exist. Are you already getting blocked for sending something matching the typical XML/HTML markup? It might be impossible to bypass that WAF without using encoding tricks but the success of encoding your payload heavily depends on the application and what it is doing with the input.
You could try adding more than just an x between < and > (i.e. <x xx x test>). Maybe there is a simple regex rule searching for alphanumeric characters but not for blank spaces. You could also try adding multiple spaces and so on and so forth.

There are an unlimited amount of things you can try in order to understand the WAF. The more time you are investing here, the more knowledge you will have when you are going to the next step, which is crafting the final payload.

At the bigger company mentioned above, there were two categories of things the WAF is matching. One group will be blocked instantly, the second group of payloads will be rewarded with a block after sending them the second time.

The first group contained obvious malicious payloads like the examples above. Group two seemed to be payloads that consist of a non-existing html tag like <x> with an actual payload/event handler that would work on a different existing html tag.

Full example:

"><x onerror="alert(document.domain)">

Requesting this a second time will get us banned.

So I tried the other way around: Use a valid html tag with an event handler that will be triggered in combination with the html tag. This time the event handler will be some random stuff.

Example:

"><img src= onerror="x()">

I didn't get banned for this at all.

Step 3: Using our knowledge to craft a working XSS payload

We do have at least two options we can take a look at now. Rather we're finding a html tag their WAF doesn't know about or we can find an interesting payload they don't understand. Because remember, <x> with a real payload was a block at the second request, <img> with a crappy payload in the onload event handler was fine for the WAF.
Overall, it seems like they are searching for a list of html tags with a pattern of payloads.

Option 1, searching for an element their WAF doesn't know didn't work in my case. It was interesting, they did really know about every element I did throw in the parameter, and I tried a lot of elements in the HTML reference over at w3schools.

So my last option was to trick their event handler payload detection. I'm confident, that I tried every payload ever shared publicly on the internet, starting from "><a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa aaaaaaaaaa href=j&#97v&#97script:&#97lert(1)>ClickMe,
heading over to ><iframe> src="data:text/html,%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E"></iframe>
and finally ending with the javascript event handler Onerror="top[8680439..toString(30)](document.cookie)".

OWASP has a great collection of XSS payloads on their website.
Also, if the input is being parsed into JavaScript code directly, working with the self object in combination with hex encoding is worth a try in order to bypass the firewall.

Unencoded example:

self['alert']('XSS')

alert = \x61\x6c\x65\x72\x74
XSS = \x58\x53\x53

Final encoded payload:

self['\x61\x6c\x65\x72\x74']('\x58\x53\x53')

At the time of writing this, payloads like this bypass the OWASP ModSecurity Core Rule Set at a paranoia level of 1.
(Shoutout to theMiddle for bringing the concept of abusing the self object this way to my attention!)

Back to our example, this is the final URL:

https://not-evil-corp.com/companies/e-corp-labs?language=de_DE"><img src=x Onerror="top[8680439..toString(30)](document.cookie)">

The real scenario was very similiar, but the attribute I could escape out of was in the head section of the website, which is why I also had to close the head and open the body tag before using the img tag.

Takeaway

You can do better than your automated tools by just investing some time to understand the WAF ruleset.

Interestingly enough, I was searching for the company name in combination with XSS on Google to see if there was a disclosed report or writeup anywhere on the web because I could not find a bug bounty program yet. What I found was the exact same vulnerability on Open Bug Bounty labeled as fixed. A Web Application Firewall is not a fix for a vulnerability, q.e.d.

Both reports are work in progress at the time of writing this, which is why I'm not writing about the actual examples and companies directly, even though I'd be proud to present the company names. Also, this is not a bulletproof 1:1 step-by-step tutorial on how to bypass any WAF being triggered when trying to exploit an XSS vulnerability. I'm just describing the process of my attempt to bypass several WAFs.
Edit 6. August 2018: Hex encoded self object payloads bypass the OWASP CRS with a paranoia level below 2, not 3.

Main Image Credit : The awesome piece of artwork used to head this article is called 'Hacking' and it was created by graphic designer Dani Player.