Penetration Testing and Web Application Firewalls
A Web Application Firewall (WAF) is a defence-in-depth mitigation against common web attacks by monitoring and filtering HTTP traffic. WAFs work by analysing the plaintext content of HTTP messages between the client and server to determine if the given message is malicious. If it’s deemed to be malicious, the WAF stops the message from reaching the application server; otherwise it passes the message on to the server without modifying it.
For the most part, WAFs defend against injection attacks (such as SQL injection, cross-site scripting, command injection, etc) and simple XXE attacks. They can also help protect web apps by blocking access to certain pages (like an administrative WordPress login), restricting access to the site based on IP or geolocation, and even some simple bot controls to manage which – and how – bots can interact with the site.
To achieve this, the WAF inspects the HTTP message’s parameters, body, and headers against a predefined set of rules. These rules are then used to determine if a message is malicious or not.
You can find three different types of WAF: network based, host based, and – the most common – cloud based. Network based WAFs are hardware which sit between the Internet and web server in a local datacentre and analyse packets “on-the-wire”. They offer the lowest latency of the different options but require more maintenance.
Host based WAFs run on the web server itself, allowing for greater flexibility of the WAF’s rules. However, they are of course tightly coupled to the web server and application making abstraction hard and require significantly more development and design.
The most common and straightforward Web Application Firewall is cloud hosted, like AWS WAF or Cloudflare. These are basically reverse proxies which intercept and inspect the HTTP messages and then, if they pass all the selected rules, forward the packets between the client and server. While they often add latency to the traffic flow, they’re a simple solution managed full-time by third party security experts. This does of course mean you extend your application’s trust boundary to include the WAF provided, but more often than not these third parties can manage the WAFs more efficiently than your company doing it themselves.
One thing to note is that many WAF providers claim to protect against the most serious OWASP Top 10 vulnerabilities. Let’s not mince words: this is borderline security theatre. It defends against some injection, XSS, and XXE attacks – and poorly at that as you’ll soon see. Additionally, the term “injection” is so broad to almost be useless in this context because the WAF needs to protect against specific types of injection, not just the idea of injection vulnerabilities.
WAFs have some serious limitations. For the most part, they’re black box; a third party manages the individual rules. If they aren’t managed by a third party, they require a large amount of effort to maintain as the rules keep changing and new bypasses are being constantly found. It’s a real catch 22.
They’re also defending very complex languages and syntaxes purely using static analysis. This makes things like JavaScript hard to manage as not only are new features being constantly added, but there are so many different ways to achieve the same outcome – see below for a JavaScript bypass developed in less than an hour. Even something like SQL can be hard – the following statement is valid SQL:
/*S*/s/*--*/E/*-- red herring*/l/**/ect /**/* fR/**/Om users;
While this quite simple case (alternating case and breaking keywords with comments) is likely considered by all major WAF providers, it shows that there are edge cases that often aren’t considered at first when designing blacklist systems like WAFs.
Here’s an example. Earlier this year on an engagement where the client couldn’t whitelist us against their WAF, we were testing for XSS against a web app. Because the WAF was dropping our packets, we couldn’t fully determine whether the app was vulnerable or not. Because this was a high-assurance application, I set out to try bypass the Cloudflare WAF in order to fully test the app. After not even an hour of tinkering and tweaking, I crafted the following payload:
<svg/onload="new Function(atob('YWxlcnQoJ1hTUycp'))()">
It takes advantage of some of JavaScript’s newer high-level functionality to bypass usual blacklist of functionality to create an alert box and prove the application was susceptible to XSS.
Cloud based WAFs also leave room for one big issue – if the web server isn’t configured to block non-WAF originated traffic, the mitigation can be bypassed. All an attacker needs to do it scan the internet with the Host HTTP header set accordingly and if a server responds then it can attack your web server while completely ignoring the WAF.
Because a WAF can hide vulnerabilities in the application, and can be bypassed by most dedicated attackers, we always ask our clients to disable it during our pentesting. This allows our tooling to scan the application without us having to manually attempt to bypass it. It’s often time consuming and sometimes can’t be done in the engagement window we have. In cases where is can’t be disabled, we just leave a caveat in the report stating we couldn’t perform all our test cases.
Web Application Firewalls are a good defence-in-depth mechanism. While they shouldn’t be exclusively relied on to prevent injection attacks, they’re a relatively inexpensive secondary mitigation which can deter script kiddies and attackers looking for low-hanging fruit.