OSS WAF stack using Coraza, Caddy, and Elastic

Juan Pablo Tosso
6 min readAug 29, 2022

--

OWASP Coraza v3 is reaching a stable point, and it’s time to show the people how to harness its power.

In this tutorial, I’m going to teach you how to easily set up a production-ready environment for your web applications and APIs using OWASP Coraza WAF.

About the stack

We are going to set up Caddy as a reverse proxy to serve two juiceshop services protected by OWASP Coraza and OWASP Core Ruleset. A logging service is going to upload audit events to elastic and make them available in Kibana.

Preparations

The creation of the docker-compose.yml is separated into multiple steps, make sure to create each service and volume as specified.

Step 1: Setup Juiceshop

Add our test domain to your hosts table /etc/hosts or C:\Windows\Drivers\etc\hosts). They should point to 127.0.0.1 or your Docker IP address. The hostnames we are going to expose are:

  • prod.lab.external:80
  • testing.lab.external:80
➜  ~ cat /etc/hosts
# ...
127.0.0.1 prod.lab.external testing.lab.external

Add the OWASP juice-shop service to the docker-compose.yml:

services:
juiceshop_prod:
image: bkimminich/juice-shop
juiceshop_testing:
image: bkimminich/juice-shop

Step 2: Setup Caddy

To set up Caddy we could use the official coraza-caddy Docker image, but we need an updated version of Coraza for better performance. To do this, we are going to create a ./caddy/Dockerfile with the following content.

Create a Caddyfile with the following configuration:

{
order coraza_waf first
}
prod.lab.external:80 {
reverse_proxy juiceshop_prod:3000
}
testing.lab.external:80 {
reverse_proxy juiceshop_testing:3000
}

Now let's add our newly created service Caddy instance to docker-compose.yml. We are going to consider the audit volume to store our share audit logs with the logger service, we are also sharing ./Caddyfile for configurations and ./ruleset for the WAF rules.

Don’t forget to declare the audit volume.

Now if we start our docker-compose.yml file, which contains the two juiceshop apps and the Caddy web server, we should be able to navigate to http://127.0.0.1:8080/ and obtain the following result:

Step 3: Setup Elastic + Kibana

To set up elastic and kibana, it’s important to copy the following settings exactly as they are. Otherwise, you might have a problem connecting elastic to kibana or face performance issues.

After merging the two juiceshop apps, caddy, elastic, and kibana, we should be able to navigate into Kibana user interface and see:

docker-compose can take more than 10 minutes to set up elastic and kibana, depending on your hardware.

Step 4: Log processor

We are going to use a simple log processor, friendly with docker. Our processor must detect new files in the audit directory, parse the JSON data, and create a consistent JSON document that will be uploaded to elastic.
For this project, we are using two required libraries, watchdog and elasticsearch: pip install elasticsearch watchdog

Now that we have our working python script, it’s time to make it part of our docker project. First, we are going to create a logger directory in our workdir with the following files:

  • logger/logger.py: our logger file
  • logger/entrpoint.sh: empty script we are going to use to call our code
  • logger/Dockerfile: docker instructions to build the log processor container

entrypoint.sh content:

#!/bin/bash
PYTHONUNBUFFERED=1 python -u /app/logger.py $@

Dockerfile content:

FROM python:latest
RUN pip install watchdog elasticsearch
COPY logger.py /app/logger.py
COPY entrypoint.sh /app/entrypoint.sh
ENTRYPOINT [ "/app/entrypoint.sh"]

Then we are going to add our logger service to our docker-compose:

Now we should have the following services: two juiceshops, caddy, elastic, kibana, and the logger.

Step 5: OWASP Core Ruleset

OWASP Coreruleset’s structure allows us to control multiple websites using the same CRS rules directory. Using a smart distribution of files and directories, we can easily handle multiple CRS settings.
For this case, we are going to use the following structure:

  • /ruleset/sites/prod.lab.external/1_crs_setup.conf: Downloaded from here
  • /ruleset/sites/testing.lab.external/1_crs_setup.conf: Downloaded from here
  • /ruleset/coraza.conf: Downloaded from here
  • /ruleset/coreruleset/: Using automatic CRS integration

You can automate the previous directory and file creations using the following commands:

mkdir -p ruleset/sites/prod.lab.external
mkdir -p ruleset/sites/internal.lab.external
wget https://raw.githubusercontent.com/corazawaf/coraza/v3/main/coraza.conf-recommended -o ruleset/coraza.conf
git clone https://github.com/coreruleset/coreruleset ruleset/coreruleset
cp ruleset/coreruleset/crs-setup.conf.example ruleset/sites/prod.lab.external/1_crs_setup.conf
cp ruleset/coreruleset/crs-setup.conf.example ruleset/sites/testing.lab.external/1_crs_setup.conf

Ruleset is already created as a shared volume for Caddy in our docker-compose file, so everything we create in our ./ruleset directory will be shared with our caddy container.

Now we have to set up the rule profiles per application. First, we load Coraza global settings, then we load the CRS-specific settings, and finally, the CRS rules.
Add the rules using the following Caddyfile.

{
order coraza_waf first
}
prod.lab.external:80 {
coraza_waf {
load_owasp_crs
directives `
Include /ruleset/coraza.conf
Include /ruleset/prod.conf
Include @owasp_crs/*.conf
SecRuleEngine On
`
}
reverse_proxy juiceshop_prod:3000
}
testing.lab.external:80 {
coraza_waf {
load_owasp_crs
directives `
Include /ruleset/coraza.conf
Include /ruleset/testing.conf
Include @owasp_crs/*.conf
SecRuleEngine On
`
}
reverse_proxy juiceshop_testing:3000
}

Step 6: Connect the pieces

Now with all pieces connected, your docker-compose.yml should look like this:

To create the Kibana data types, we have to create some demo data. To do so, we have to trigger some Coraza audit logs in the production app. First, we are going to start our docker containers using the command docker-compose up. You can add -d to send the containers to the background.
After all your containers are running, send some attack to an exposed juiceshop, for example: curl http://prod.juiceshop.external/?id='%20''='
The previous payload should trigger a SQL injection event and create an audit log that we should be able to catch by running the following steps:

  1. Go to http://127.0.0.1:5601/app/management/kibana/dataViews (Stack Management → Data Views)
  2. Select “Create data view”
  3. Create an index pattern that matches the indices names, like coraza_*

4. Select “Save data view to Kibana”

5. Now we should see a list of fields like client_ip, client_port, etc

Now go to the “Discover” view, and we should see our recent event. If your recently created event is here, it means everything worked, and we are done :)

Final words

With a few tweaks, you can port this project into any application. You can even use Caddy to proxy apps outside of your docker environment.

You can download the final project from this repo.

git clone --recursive https://github.com/jptosso/coraza-demo-stack
cd coraza-demo-stack
docker-compose up

It might take a few minutes to startup, as it takes a lot of resources to prepare the elastic indices. After you see everything ok, navigate to http://127.0.0.1:5601, and you should see that Kibana is ready. A WordPress installation will be available on http://127.0.0.1:8080/

Links

About the author

Juan Pablo Tosso is the author and co-leader of OWASP Coraza. He works as a security research engineer at Traceable. When he is not having fun with his children, he is reading, writing, or doing Web Application stuff.

Find me on Twitter as @jptosso

FAQ

How does Coraza compare to Modsecurity in performance?

ModSecurity and Coraza are similar in performance, some use cases are faster in Coraza and some in Modsecurity.

Requests are not being denied

If you want to deny the malicious requests, you have to update your ruleset/coraza.conf and change SecRuleEngine from DetectOnly to On.

Can Caddy + Coraza escale?

You can use a load balancer to balance Caddy servers, no additional settings are required. In the future, we might be able to provide a stable ingress controller using Coraza.
There is also an experimental project for HAPROXY that can be balanced at the backend using SPOA, see this link.

I have implemented Caddy + Coraza + CRS, now what?

After implementing Coraza + Caddy + CRS you should start tunning your false positives, click here to learn more.

--

--