Skip to main content

Web application firewall with ModSecurity

Context

As soon as you publish an endpoint to internet, it will be scanned by bots and/or malicious people. Web Application Firewall is a good solution to protect your applications and endpoints from those scanning.

K8saas provide a WAF by default based on modsecurity:

img

And it is confired with the OWASP Core Rule set v3.3.2 (last stable version):

img

This default configuration covers:

  • SQL Injection (SQLi)
  • Cross Site Scripting (XSS)
  • Local File Inclusion (LFI)
  • Remote File Inclusion (RFI)
  • PHP Code Injection
  • Java Code Injection
  • HTTPoxy
  • Shellshock
  • Unix/Windows Shell Injection
  • Session Fixation
  • Scripting/Scanner/Bot Detection
  • Metadata/Error Leakages

Use case

Concept

To increase the security of an application or more globally of an infrastructure, you may have to perform a Threat Model like:

Alt text

In green, you could see a boundaries that segregate areas. Inside an area, a system trusts all sub-systems. To protect these trust boundaries, it's very important to use perimeter protections mechanisms.

To protect an application efficiently, you should combine several mechanisms, from the lowest level, to the highest level.

Let use the OSI model:

Alt text

There is several types of firewalls:

  • The common network firewall works on level 3 and 4 to allow or deny network communication. For example: I would like to allow TCP:443 from internet to my application
  • WAF are an applicative firewall. It means that it works on the applicative layers: 5, 6 and 7. For example: I would like to block specific user agents or URI.

This documentation describes how to enable a WAF using a nginx ingress controller with k8saas.

Synthesis of CRS rules

Rule NameDescription
REQUEST-01-COMMON-EXCEPTIONSSome rules are quite prone to causing false positives in well established software, such as Apache callbacks or Google Analytics tracking cookie. This file offers rules that will allow the transactions to avoid triggering these false positives.
REQUEST-10-IP-REPUTATIONThese rules deal with detecting traffic from IPs that have previously been involved with malicious activity, either on our local site or globally.
REQUEST-12-DOS-PROTECTIONThe rules in this file will attempt to detect some level 7 DoS (Denial of Service) attacks against your server.
REQUEST-13-SCANNER-DETECTIONThese rules are concentrated around detecting security tools and scanners.
REQUEST-20-PROTOCOL-ENFORCEMENTThe rules in this file center around detecting requests that either violate HTTP or represent a request that no modern browser would generate, for instance missing a user-agent.
REQUEST-21-PROTOCOL-ATTACKThe rules in this file focus on specific attacks against the HTTP protocol itself such as HTTP Request Smuggling and Response Splitting.
REQUEST-30-APPLICATION-ATTACK-LFIThese rules attempt to detect when a user is trying to include a file that would be local to the webserver that they should not have access to. Exploiting this type of attack can lead to the web application or server being compromised.
REQUEST-31-APPLICATION-ATTACK-RFIThese rules attempt to detect when a user is trying to include a remote resource into the web application that will be executed. Exploiting this type of attack can lead to the web application or server being compromised.
REQUEST-41-APPLICATION-ATTACK-SQLIWithin this configuration file we provide rules that protect against SQL injection attacks. SQLi attackers occur when an attacker passes crafted control characters to parameters to an area of the application that is expecting only data. The application will then pass the control characters to the database. This will end up changing the meaning of the expected SQL query.
REQUEST-43-APPLICATION-ATTACK-SESSION-FIXATIONThese rules focus around providing protection against Session Fixation attacks.
REQUEST-49-BLOCKING-EVALUATIONThese rules provide the anomaly based blocking for a given request. If you are in anomaly detection mode this file must not be deleted.
RESPONSE-50-DATA-LEAKAGES-IISThese rules provide protection against data leakages that may occur because of Microsoft IIS
RESPONSE-50-DATA-LEAKAGES-JAVAThese rules provide protection against data leakages that may occur because of Java
RESPONSE-50-DATA-LEAKAGES-PHPThese rules provide protection against data leakages that may occur because of PHP
RESPONSE-50-DATA-LEAKAGESThese rules provide protection against data leakages that may occur genericly
RESPONSE-51-DATA-LEAKAGES-SQLThese rules provide protection against data leakages that may occur from backend SQL servers. Often these are indicative of SQL injection issues being present.
RESPONSE-59-BLOCKING-EVALUATIONThese rules provide the anomaly based blocking for a given response. If you are in anomaly detection mode this file must not be deleted.
RESPONSE-80-CORRELATIONThe rules in this configuration file facilitate the gathering of data about successful and unsuccessful attacks on the server.

What to do ?

By default, the nginx ingress controller already has a WAF configured, but not enabled.

To enable it, you just have to add the following lines to the annotations section of your ingress:

annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecAuditLog /dev/stdout
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf

HOWTO

Test a minimal configuration with a WAF ?

A hello world with a WAF is available here

Special case: whitelist IP

note

we strongly recommend to use nginx configuration to whitelist an IP rather than using WAF

Use the following configuration to whitelist an ip here: 192.168.1.100 in your ingress :

annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecAuditLog /dev/stdout
SecRule REMOTE_ADDR "@ipMatch 192.168.1.100" "phase:1,id:'981033',t:none,nolog,pass,ctl:ruleEngine=Off"
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
warning

Make the SecRule line is above Include

How to disable a rule by ID

Update the annotation of the ingress with the appropriate IDs:

annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecAuditLog /dev/stdout
SecRuleRemoveById "941000-941999"
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf

How to override specific rule

Update the annotation of the ingress with the rule. In that case allow http verb GET HEAD POST OPTIONS PUT PATCH DELETE

annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecAuditLog /dev/stdout
SecAction "id:900200,phase:1,nolog,pass,t:none, setvar:tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE"
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf

How to disable a rule by Tag

Update the annotation of the ingress with the appropriate tag:

annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecAuditLog /dev/stdout
SecRuleRemoveByTag "platform-iis"
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf

Update Modsecurity Paranoia level

The CRS project sees the 4 Paranoia Levels as follows:

  • PL 1: Baseline Security with a minimal need to tune away false positives. This is CRS for everybody running an HTTP server on the internet. If you encounter a false positive on a PL 1 system, please report it via GitHub.
  • PL 2: Rules that are adequate when real customer data is involved. Perhaps an off-the-shelf online shop. Expect false positives and learn how to tune them away.
  • PL 3: Online banking level security with lots of false positives. From a project perspective, false positives are accepted here, so you need to be able to help yourself by writing rule exclusions.
  • PL 4: Rules that are so strong (or paranoid) they are adequate to protect the crown jewels. Use at your own risk and be prepared to get a large number of false positives.

Update the annotation of the ingress with the appropriate tx.paranoia_level value:

annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecAuditLog /dev/stdout
SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=4"
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf

HOWTO Monitor WAF ?

K8SAAS provides a built-in WAF Dashboard in our observability stack.

To access it:

  • Access to Cluster Monitoring Portal
  • Look at "k8saas / WAF" dashboard
  • Save as another name (keeping the tags)
  • Update the view to send alerts to the right Log analytics: link

Then you see:

img

Look at the WAF logs ?

First, access to your dedicated log analytics. The documentation of log analytics is here

Then, the logs are accessible using the following query:

waf_CL
| project TimeGenerated, waf_ModSecMsg_s, waf_server_s, waf_ruleId_s
| sort by TimeGenerated asc

If there are no logs, you can follow the previous HOWTO: "Test a minimal configuration with a WAF"

How to access Audit Logs

By default only error logs are available in Log Analytics. If you wish to access "Audit logs" also to get more information on these errors, you should add the following annotation to your ingress:

Update the annotation of the ingress with the appropriate SecAudit values:

annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecAuditLog /dev/stderr
SecRuleEngine On
SecAuditLogType Serial
SecAuditEngine RelevantOnly
SecAuditLogParts ABIJDEFHZ
SecAuditLogFormat JSON

Explanation of these annotations :

  • SecAuditLog /dev/stderr: This directs the audit logs to the standard error output.
  • SecRuleEngine On: This turns on the ModSecurity rule engine, enabling it to process rules.
  • SecAuditLogType Serial: This sets the type of audit logging to 'Serial', meaning all audit data is logged in a single continuous line.
  • SecAuditEngine RelevantOnly: This tells the ModSecurity audit engine to log only relevant transactions, i.e., transactions that triggered a rule.
  • SecAuditLogParts ABIJDEFHZ: This specifies the parts of the transaction data to be logged. Each letter represents a different part (A=audit log header, B=request headers, etc.). Offical Documentation: https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v2.x)#user-content-SecAuditLogParts)
  • SecAuditLogFormat JSON: This sets the format of the audit log to JSON.

Now you can :

  • still get the error log in the log analytics WAF_CL table :

    waf_CL
    | project TimeGenerated, waf_ModSecMsg_s, waf_server_s, waf_ruleId_s
    | sort by TimeGenerated asc

    Here note down the ruleId.

  • also all the audit log in log analytics INGRESS_CL table:

    ingress_CL
    | where transaction_messages_s contains '"ruleid":"XXX"'

    changing XXX with your wanted ruleid

info

Careful ! The ruleid is the id for the rule itself, ther is no id for the mosecurity "event" itself. It is important to also take into account the time range of the query.

Next Steps

Making Rules

Here is the official documentation

Audit my WAF

img

To audit your WAF configuration, we recommend to use gotestwaf. This tool generates a table and a PDF to understand the level of protection that your WAF offers.

Prerequisites:

  • you should have a k8saas cluster
  • an ingress should be configured with your WAF

Here is the steps to first install and run a test over k8saas:

# pull image on k8saas
kubectl create deployment --image=wallarm/gotestwaf gotestwaf -n dev -- /bin/sh -ec "while :; do echo '.'; sleep 5 ; done"

# start the audit
export YOUR_URL=https://the_url_you_want_to_test.com
export GOTESTWAF_POD_NAME=$(kubectl get pods -A --no-headers -o custom-columns=":metadata.name"|grep gotestwaf)
kubectl -n dev -c gotestwaf exec -it $GOTESTWAF_POD_NAME -- /app/gotestwaf --url=$YOUR_URL

Once done you should see an output like

Negative Tests:
+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+
| TEST SET | TEST CASE | PERCENTAGE, % | BLOCKED | BYPASSED | UNRESOLVED | SENT | FAILED |
+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+
| community | community-lfi | 100.00 | 8 | 0 | 0 | 8 | 0 |
| community | community-rce | 100.00 | 12 | 0 | 0 | 12 | 0 |
| community | community-sqli | 100.00 | 32 | 0 | 0 | 32 | 0 |
| community | community-xss | 100.00 | 524 | 0 | 0 | 524 | 0 |
| community | community-xxe | 100.00 | 2 | 0 | 0 | 2 | 0 |
| owasp | crlf | 100.00 | 8 | 0 | 0 | 8 | 0 |
| owasp | ldap-injection | 85.71 | 12 | 2 | 2 | 16 | 0 |
| owasp | mail-injection | 83.33 | 15 | 3 | 6 | 24 | 0 |
| owasp | nosql-injection | 85.71 | 18 | 3 | 9 | 30 | 0 |
| owasp | path-traversal | 87.06 | 74 | 11 | 25 | 110 | 0 |
| owasp | rce | 100.00 | 14 | 0 | 4 | 18 | 0 |
| owasp | rce-urlparam | 100.00 | 9 | 0 | 0 | 9 | 0 |
| owasp | shell-injection | 85.71 | 36 | 6 | 6 | 48 | 0 |
| owasp | sql-injection | 79.66 | 47 | 12 | 13 | 72 | 0 |
| owasp | ss-include | 80.77 | 21 | 5 | 14 | 40 | 0 |
| owasp | sst-injection | 80.95 | 34 | 8 | 22 | 64 | 0 |
| owasp | xml-injection | 100.00 | 13 | 0 | 0 | 13 | 0 |
| owasp | xss-scripting | 83.15 | 74 | 15 | 47 | 136 | 0 |
| owasp-api | graphql | 100.00 | 6 | 0 | 0 | 6 | 0 |
| owasp-api | graphql-post | 100.00 | 4 | 0 | 0 | 4 | 0 |
| owasp-api | grpc | 0.00 | 0 | 0 | 0 | 0 | 0 |
| owasp-api | rest | 100.00 | 2 | 0 | 0 | 2 | 0 |
| owasp-api | soap | 100.00 | 2 | 0 | 0 | 2 | 0 |
+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+
| DATE: | PROJECT NAME: | AVERAGE SCORE: | BLOCKED (RESOLVED): | BYPASSED (RESOLVED): | UNRESOLVED (SENT): | TOTAL SENT: | FAILED (TOTAL): |
| 2022-05-03 | GENERIC | 93.28% | 967/1032 (93.70%) | 65/1032 (6.30%) | 148/1180 (12.54%) | 1180 | 0/1180 (0.00%) |
+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+

Positive Tests:
+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+
| TEST SET | TEST CASE | PERCENTAGE, % | BLOCKED | BYPASSED | UNRESOLVED | SENT | FAILED |
+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+
| false-pos | texts | 0.00 | 51 | 0 | 0 | 51 | 0 |
+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+
| DATE: | PROJECT NAME: | FALSE POSITIVE SCORE: | BLOCKED (RESOLVED): | BYPASSED (RESOLVED): | UNRESOLVED (SENT): | TOTAL SENT: | FAILED (TOTAL): |
| 2022-05-03 | GENERIC | 0.00% | 51/51 (100.00%) | 0/51 (0.00%) | 0/51 (0.00%) | 51 | 0/51 (0.00%) |
+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+

report is ready: reports/waf-evaluation-report-2022-May-03-16-03-43.pdf

Then to get the PDF report on your laptop

# Copy the PDF on local
export PDF_FILE_NAME=the_filename_generated_by_the_previous_command
kubectl cp dev/$GOTESTWAF_POD_NAME:/app/reports/$PDF_FILE_NAME /tmp/$PDF_FILE_NAME -c gotestwaf

Then you should have a report like: WAF report

note

Depending on the modsecurity paranoia level, you will have a different score. With CRS 3.3.2, Level 1 gives D- 61.6/100 example here and Level 4 gives C 73.5/100 - example provided below