Which is the best way to test a WAF Rule Set? Create the most vulnerable PHP script in the world and try all possible techniques! In the screenshot above we have: in the top left pane there’s my ugly web application (it’s just a PHP script that executes commands):

<?php

echo 'ok: ';

print_r($_GET['c']);

system($_GET['c']);

In the bottom left pane you can see a test of Remote Command Execution on my website protected by Sucuri WAF (test1.unicresit.it). As you can see Sucuri blocks my request with reason “An attempted RFI/LFI was detected and blocked”. This reason is not completely true but the good news is that the WAF blocked my attack (I don’t even know why a firewall should tell me the reason for a blocked request, but there should be a reason… for sure).

The right pane is the most interesting of all, because it shows the same request but using the “question mark” as a wildcard. The result is frightening… The request is accepted by Sucuri WAF and my application executes the command that I put in c parameter. Now I can read the /etc/passwd file and even more… I can read the PHP source of application itself, I can execute reverse shell using netcat (or as I love to call it: /???/?c ), or I could execute programs like curl or wget in order to reveal the real IP Address of the web server that make me able to bypass the WAF by connecting directly to the target.

I don’t know if this happens because I missed something on my Sucuri WAF configuration, but it not seems… I’ve asked at Sucuri if it’s an attended behavior and if they configure a default “low paranoia level” in order to avoid false positives, but I’m still waiting for an answer.

Please, keep in mind that I’m doing this test using a stupid PHP script that doesn’t represent a real scenario. IMHO you shouldn’t judge a WAF based on how many requests it blocks, and Sucuri is not less secure just because can’t totally protect an intentionally vulnerable website. Necessary clarification done!

ModSecurity OWASP CRS 3.0

I really love ModSecurity, I think that the new libmodsecurity (v3) used with Nginx and the Nginx connector is the best solution that I have ever used in order to deploy a Web Application Firewall. I’m also a big fan of the OWASP Core Rule Set! I use it everywhere but, if you don’t know well this rule set, you need to pay attention to a little thing called love.. ehm sorry Paranoia Level!

Paranoia Level for dummies

The following “schema” that you can find here is a good overview of how each level works on “REQUEST PROTOCOL ENFORCEMENT” rules. As you can see with a PL1 a query string can contains only ASCII characters in the range 1–255 and it becomes more restrictive until the PL4 that blocks everything that isn’t an ASCII character in a very small range.

# -=[ Targets and ASCII Ranges ]=-

#

# 920270: PL1

# REQUEST_URI, REQUEST_HEADERS, ARGS and ARGS_NAMES

# ASCII: 1-255

# Example: Full ASCII range without null character

#

# 920271: PL2

# REQUEST_URI, REQUEST_HEADERS, ARGS and ARGS_NAMES

# ASCII: 9,10,13,32-126,128-255

# Example: Full visible ASCII range, tab, newline

#

# 920272: PL3

# REQUEST_URI, REQUEST_HEADERS, ARGS, ARGS_NAMES, REQUEST_BODY

# ASCII: 32-36,38-126

# Example: Visible lower ASCII range without percent symbol

#

# 920273: PL4

# ARGS, ARGS_NAMES and REQUEST_BODY

# ASCII: 38,44-46,48-58,61,65-90,95,97-122

# Example: A-Z a-z 0-9 = - _ . , : &

#

# 920274: PL4

# REQUEST_HEADERS without User-Agent, Referer, Cookie

# ASCII: 32,34,38,42-59,61,65-90,95,97-122

# Example: A-Z a-z 0-9 = - _ . , : & " * + / SPACE

let’s do some test with all levels!

Paranoia Level 0 (PL0)

A paranoia level 0 means that many rules are disabled, so it’s absolutely normal that our payload can lead to a Remote Command Execution without any problem. Don’t panic :)

SecAction "id:999,\

phase:1,\

nolog,\

pass,\

t:none,\

setvar:tx.paranoia_level=0"

RCE accepted by ModSecurity with PL0 (don’t panic, it’s ok)

A paranoia level 1 in ModSecurity means “flawless rules of high quality with virtually no false positives” but it’s also too much permissive. You can find a list of rules grouped by paranoia level at netnea website: https://www.netnea.com/cms/core-rule-set-inventory/

Paranoia Level 1 and 2 (PL1, PL2)

I’ve grouped levels 1 and 2 because their differences (as you can see in the schema above) doesn’t affect our goal, all behaviors are the same as described below.

SecAction "id:999,\

phase:1,\

nolog,\

pass,\

t:none,\

setvar:tx.paranoia_level=1"

with PL1 (and PL2) ModSecurity obviously blocks my request for “OS File Access Attempt” (930120). But what if I use the question mark as a wildcard? The request is accepted by my WAF:

with PL1 and PL2 my RCE attack was not blocked and I can read /etc/passwd

This happens because the “question mark”, the “forward slash” and the “space” are in the accepted range of characters on rules 920271 and 920272. Moreover, using “question marks” instead of command syntax make me able to evade “OS Files” filters that intercept common commands and files of Operating Systems (such as /etc/passwd in our case).

Paranoia Level 3 (PL3)

This level of paranoia has a plus: it blocks request containing characters like “?” more than n times. In fact, my requests have been blocked as “Meta-Character Anomaly Detection Alert — Repetitive Non-Word Characters”. this is cool! nice job ModSecurity, you win a teddy bear! 🐻 But unfortunately, my web app is so ugly and vulnerable that I can use less question mark and read the passwd file anyway using this syntax: c=/?in/cat+/et?/passw?

As you can see, using just 3 “?” question mark I can evade this paranoia level and read the passwd file inside the target system. OK, this doesn’t mean that you have to set your paranoia level to 4 always and unconditionally. Keep in mind that I’m testing it with a really stupid PHP script that doesn’t represent a real scenario… I hope…

Now everybody knows that 42 is the answer to life, the universe and everything. But what about: “Will you evade the OWASP Rule Set at paranoia level 4?”

Paranoia Level 4 (PL4)

basically no, I can’t. All characters outside the range a-z A-Z 0–9 are blocked! No way… and trust me, when you need to execute a command in order to read files, there’s a 90% of probabilities that you need a “space” char or a “forward slash” 😉

Do you want more?

Second part of this article: https://medium.com/@themiddleblue/web-application-firewall-waf-evasion-techniques-2-125995f3e7b0

Final thoughts

Back to static HTML pages… it’s the fastest way to improve the security of your web application! 🤓 It’s hard to say what’s the best configuration to avoid WAF evasion, or what’s the best paranoia level to use. IMHO, we shouldn’t trust in a rule set evenly distributed on a web application. Indeed I think we should configure our WAF rules contextualized per application functionality.

Anyway, when you write a new SecRule on your ModSecurity or something like, keep in mind that probably there’re many ways to elude your filter / regular expression. So write it thinking of “how can I evade this rule?”.

From my bookmarks

Learn more about ModSecurity Rules: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual

Apache ModSecurity tutorial by netnea: https://www.netnea.com/cms/apache-tutorials/

SpiderLabs Blog: https://www.trustwave.com/Resources/SpiderLabs-Blog/

ModSecurity v3 Github: https://github.com/SpiderLabs/ModSecurity/tree/v3/master

Contacts

https://twitter.com/Menin_TheMiddle

https://github.com/theMiddleBlue