Background
Web application firewalls are commonly used to defend web applications (or mobile applications, APIs, etc) against known attacks.
At a basic level, they work by having a large set of rules which defines what bad or malicious requests to the site look like. You would set it up against a website, leave it in reporting-only mode for a period of time to ensure no legitimate traffic will be blocked by it, then enable it to block anything malicious.
There are many WAF providers, including ones built into web servers which you can run on your own infrastructure, ones offered by major infrastructure vendors, along with dedicated cloud providers. However, can you see a problem with them? They’re only as good as their ruleset, and it’s impossible to enumerate every single variation of what malicious traffic will look like, especially without impacting legitimate traffic. Yes, that even includes ones marketed with fancy machine learning or “AI” algorithms. Limitations
We regularly run into a few misconceptions with clients:
- They’re sometimes treated as a magic set-and-forget solution to protect against all web application vulnerabilities. They can have rulesets to protect against many of the OWASP Top 10 vulnerabilities, however a WAF won’t be able to determine the business logic around security protections. For example, if you had a user profile page, a WAF can’t tell whether it’s intended to be a public user profile, or if it contains private user details. Getting that wrong could result in a serious privacy breach.
- Even with a WAF in place, it’s rare that they are a robust protection against attacks. While they can be annoying, and slow us down if they are enabled during an engagement, it’s rare for us not to be able to bypass them with time.
How we bypass WAFs
Now for the technical part (feel free to skip this, if you’re not technical). Let’s take a recent example from a web application we were testing. We suspected there was Cross-Site Scripting due to our input being reflected back in the page, but we knew that a WAF was in place, which would make exploitation more challenging.
We started by using trial and error to establish what the WAF did not permit. In this particular case the following were not permitted:
- Function calls - it blocked both brackets and backticks; Including HTML Entity, JavaScript Unicode, and URI encodings.
- Common functions, objects, attributes - for example it blocked alert, eval, setTimeout, window, location, document, innerHTML, outerHTML, etc.
- Script tags
As part of that, we also established that the following were permitted:
- Non-script tags - This would still allow reskinning attacks
- Event Handlers - onload, onerror, etc. These are often blocked, but were not in this instance.
- iFrames - Allows embedding other sites, could be used for spoofing the system and capturing user interactions.
- srcdoc - this attribute loads content into an iFrame directly, retaining the origin of the parent domain (compared to “src”).
From here we were able to establish our key constraints for developing a working payload:
- Not being able to call functions using either brackets or backticks. This limits JavaScript execution to only attribute setting.
- Not being able to set innerHTML, outerHTML, or use window, location, or document.
- While “srcdoc=” could be present in isolation, it was blocked if
“<iframe”
was present anywhere earlier in the payload.
Lastly we establish the tools and tricks that we can use in our payload, in this case: srcdoc - This allows adding HTML to the page via an attribute.
<img src=”” onerror=”PAYLOAD”>
- This allows running any “non-mitigtated” JavaScript expressions. “data:;base64,” - This allows base64 decoding of content without requiring a function call. Base64 encoding allowed bypassing of all WAF controls.
The payload:
<img src="" onerror="xss.srcdoc='<'+'script src=data:;base64,YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ==></'+'script>'"><iframe id=xss>
Some items of note:
- The bracket/backtick (function calling) restriction was bypassed by using attribute assignments.
“<script>”
detection was defeated by splitting/joining the match value using string concatenation.- “srcdoc after iframe” was defeated by placing srcdoc before the iframe tag.
- Arbitrary script payload “alert(document.domain)” bypassed checks by being base64 encoded.
- It is noted that an external script could have been included (CSP dependent); however, self contained examples are preferable for demonstration purposes.
Where a WAF fits in
A web application firewall is only as good as its ruleset, and we regularly find ways to bypass them. It will likely be forever a cat and mouse game where WAF vendors will implement rules for known exploits, and ethical (and less than ethical) hackers will find ways to bypass those rules. We can find bypasses either using gaps in the product’s coverage, or exploiting new standards which the WAF products haven’t got complete coverage of yet.
Despite these limitations, they can be an excellent and cost effective tool as part of defence in depth. In our experience when carrying out cybersecurity testing with WAFs on (which we don’t recommend), they can significantly slow down and hinder testing speed.
If you have a WAF for an existing application or are implementing one for a new application, Pākiki Security recommends:
- Understanding their limitations, and ensuring there is a layered approach to security. A WAF bypass shouldn’t result in vulnerabilities being exploitable.
- Carrying out penetration testing and resolve the underlying vulnerabilities. If the application is in production and the vulnerabilities will take time to resolve, WAFs can be good to temporarily “soft-patch” the application.
- Using them as a detection tool, and set up alerts so that when an attacker starts to attempt to find vulnerabilities on your website, you are alerted and can increase monitoring or respond before they find a bypass and vulnerability.