Web application firewalls (WAF’s) are part of the defense in depth model for web applications. While not a substitute for secure code, they offer great options for filtering malicious input. Below is a story from a real assessment where an enterprise deployment of such a device was vulnerable to being bypassed. The vulnerability is one of a bad design and/or configuration and as an attacker it was very useful.
Testing the security of an app in its production deployment is important. While this app might have been assessed in development or QA, when you deploy live you might introduce new attack vectors due to configuration issues. Such was the case with our fictional customer Heisenberg Bank.
While firing up the assessment I quickly ran into issues fuzzing all the application points I usually go after first, see below (screenshot approximated):
Well… that’s both humorous and unsatisfying.
After a bit of investigating I knew that I was up against a WAF that was triggering on a few things:
- Rapid succession of POST request to forms
- Rapid succession of GET requests to *most* pages
- Lack of a CSRF token
- *Bad* characters
After one of these conditions were met it would block me with said error code for 5 minutes.
So, how to proceed?
The normal method of encoding payloads to bypass WAF regexes is hit or miss these days. WAF’s have come a long ways. Still, I gave it a shot, no dice.
While waiting in one of my *timeouts* I decided to do some WAF research. While going through several WAF implementation guides I found a forum that mentioned integrating a WAF with your caching service/device. It described a user’s trouble with standing up something *like* Varnish or a proxy/accelerator appliance running on a different host, and that the WAF was blocking that server. Of course the vendor promptly replied that you can whitelist devices based on IP, allowing them not be inspected by the WAF.
At this point, everything is still fine. Below is where things went bad for Heisenberg Bank and the WAF.
After reading more, I found that instead of doing a real lookup on incoming requests (something akin to REMOTE_ADDR or something similar), the WAF was looking at a *custom* HTTP header.
This is how it’s supposed to work if a user or other server contacts the WAF:
Instead, the WAF checks the request’s HTTP Headers. The specific implementation was checking the request for the header X-originating-IP.
So, who would this WAF be configured to trust? In this instance the default was… Itself!
Since I control all HTTP requests sent out of my browser I can easily add this header fooling the WAF to think I was itself, allowing me to bypass its protections completely:
After further research there are several headers that can be defined for WAF’s to whitelist (instead of doing a proper lookup):
- X-forwarded-for
- X-remote-IP
- X-originating-IP
- x-remote-addr
There is also a hit-list of *types* of addresses/configurations that *might* be whitelisted/vulnerable. (some fictitious examples below):
After figuring out the bypass, the rest of the assessment yielded many other vulnerabilities and was expedited due to the fact that I could bypass the WAF. It was as simple as having my inline interception proxy add the header to all requests.
A simple solution is having your frontend proxy strip all *non-standard* headers but then you’re still playing the cat and mouse game of blacklisting. Better yet, consult with your WAF vendor and see what headers are accepted and defaults are enabled. Then find a solution that doesn’t rely on information that the attacker can forge.
In general you can also audit the security of HTTP headers on your site using Gethead, a project from our dynamic testing team’s leader, Nathan LaFollette (@httphacker).
An awesome follower of the blog also made a Burp Suite extension that will add these headers to all your requests:
https://github.com/codewatchorg/bypasswaf
Happy hacking!