Using Caddy Webserver with OWASP Core Ruleset in Coraza
January 2024
I felt like there was not a good, in depth write up out there for using Caddy, Coraza, and the OWASP Core Ruleset. I was very interersted in this trio but I had trouble getting it to work initially using one of the documents I found online, so I decided to dive deep into the topic and document it well so others can benifit from it as well. Next I am hoping to look into the rate limiting add ons, but for now, lets look at using Caddy, Coraza, and the OWASP Core Ruleset.
Prerequisites:
I have a Ubuntu Linux 22.04 VM on an Intel architecture. You will need to have at least 1gb of ram. I attempted an install on an ec2
instance with 512mb of ram and it would error out due to not having enough ram when compiling the server. In order to build the image
the way that I did, I needed the current version of go installed (1.21.5) and xcaddy installed
(Installation from Cloudsmith Repo)
- Note: I will not be covering these installations, it is left as an exercise to the reader.
- Note 2: do not install go from your distribution's source repository, it most likely will not work as it is not the current version of go.
Compiling Caddy with Coraza WAF: Xcaddy
after the prerequisites are installed, it is now time to compile the coraza/caddy image. To build the image, run the following xcaddy
command:
xcaddy build --with github.com/corazawaf/coraza-caddy/v2
this will build the current version of caddy, along with the coraza plugin. If it executes correctly, the resulting executable, named
caddy
will be added to your current working directory. If you run the file
command on it, it should say
that it is an ELF.
Making Caddy Run on Your Machine as a Service
Here is the official caddy documentation for completing this task, I recommend reading it before proceeding (as this is where all this information comes from). To get caddy to run as a systemctl service in Linux, you need to perform the following tasks:
- Move the caddy ELF to the appropriate
/bin
directory:sudo mv caddy /usr/bin/
- Add a caddy user:
sudo useradd --system \ --gid caddy \ --create-home \ --home-dir /var/lib/caddy \ --shell /usr/sbin/nologin \ --comment "Caddy web server" \ caddy
- Create the main caddy config file, the caddy file. You do not need to fill it out at this time
You can put a single commented line in the file for the moment. Caddy needs to have this file in order for it to run.sudo nano /etc/caddy/Caddyfile
-
Add the system service file:
Inside this file, place these contentssudo nano /etc/systemd/system/caddy.service
- Save the service file, and run:
It should say that caddy is disabled.sudo systemctl status caddy
- Run the following commands:
sudo systemctl enable caddy
sudo systemctl restart caddy
This enables the caddy service on startup, restarts caddy, and displays the status of caddy. Now, it should list the status of caddy as running!sudo systemctl status caddy
Getting a Website Up and Running: The Caddyfile
Now that we have caddy configured, we must populate the caddyfile with the appropriate contents to get a website up and running.
A blank caddyfile can be found here and the official
caddy documentation I used can be found here
inside your Caddyfile /etc/caddy/Caddyfile
, add the following lines:
-
:80 { # Set this path to your site's directory. root * /var/www/html # Enable the static file server. file_server }
:80 {
This line specifies the port that your website will be served on If you have a firewall enabled, please ensure that you have a rule allowing this port. You can change this port to another unused port of your choice if you wish.root * /var/www/html
This line specifies the directory that will be served, this is where you will place the website files that you want rendered. You can choose a directory that you want, this one is commonly used so that is why I chose it.file_server
Enables fileserver for caddy (most web servers do this). More information on this topic here.
/var/www/html
directory served by caddy, lets add a website file there: sudo nano /var/www/html/index.html
For testing purposes, lets only put this in the file: CHICKENSTRIPS
. Save it and lets see if it is working!
- The first check I would recommend is to see if the site works on localhost. If you run
curl localhost
, it should return the contents of theindex.html
file. - After checking that, check it on a web browser that is on the same network that your webserver is connected to. When you are on the same network, you will want to navigate to the IP address of your webserver in your web browser. You can find your IP address of the server by using the
ip a
command. - enter the IP in your browser in this manner:
http://###.###.###.###
- note: I did have issues with chrome automatically redirecting to https and the site would not render. If that happens to you, there is a way to disable that automatic https redirect, but I will not cover that in this document. You should be able to curl the site from another machine on the same network, or use another browser like edge (I know, gross)
- note2: If you have a firewall enabled, you will need to ensure that the port you specified in the caddyfile is open, in our case, port 80
Configuring Coraza
Coraza should already be installed with caddy if you followed the previous steps, so all that remains is configuring the Coraza WAF. It is fairly simple to do thru the caddyfile. The documentation for configuring Coraza inside of caddy can be found here. (Warning, it is a little vague and confusing but I still recommend taking a look at it)
Our configurations, for now, will take place in the caddyfile.
- Add the following lines to the beginning of the caddy file:
{
order coraza_waf first
}
These lines ensure that out of other add-ins, Coraza is ran first. You know, security first! This is required for Coraza to work and will enable it.
:80 {
coraza_waf {
#Directives, rules, and coraza
#configurations go here
}
# Set this path to your site's directory.
root * /var/www/html
# Enable the static file server.
file_server
}
SecRule REQUEST_URI "@contains /admin" \
"id:1001, \
phase:1, \
deny, \
status:403, \
log, \
msg:'Access to /admin is forbidden'"
SecRule
- This is the directive that initiates a rule in ModSecurity.REQUEST_URI
- This is a variable that represents the URI (Uniform Resource Identifier) component of the incoming HTTP request.@contains /admin
- Matches if the requested URI contains `/admin`.id:1001
- Unique rule identification numberphase:1
- Specifies the phase in which the rule will be executed ModSecurity operates in multiple phases, and Phase 1 is the request phase with phase 2 being the response phase.deny
- Deny request if rule conditions are met.status:403
- If the rule triggers a 403 Forbidden HTTP status code is given in the server response.log
- Logs the rule match to the audit log.msg:'Access to /admin is forbidden'
- Specifies a custom message for the rule to be added in the log file
coraza_waf {
directives `
# Audit logs
SecAuditEngine On
SecAuditLog "/var/log/coraza.log"
SecAuditLogFormat JSON
SecAuditLogParts ABIJDEFHKZ
# Test case rules for logging
SecAction "id:1,pass,log"
SecRule REQUEST_URI "@contains /admin" \
"id:1001, \
phase:1, \
deny, \
status:403, \
log, \
msg:'Access to /admin is forbidden'"
SecResponseBodyAccess On
SecRuleEngine On
}
SecAuditEngine On
- Directive that enables ModSecurity audit engine, allowing ModSecurity to generate audit logs for requests and responses. This is where we will log the WAF rule matches.SecAuditLog "/var/log/coraza.log"
- Specifies the path to the audit log file. You can change this path if you'd like. You will need to ensure that the file exsists and has the appropriate ownership and access permissions.SecAuditLogFormat JSON
- Sets the format of the audit log entries to JSON.SecAuditLogParts ABIJDEFHKZ
- Specifies the parts of the audit log to include. Each letter represents a specific part of the audit log. There is a good chart hereSecAction "id:1,pass,log"
- Defines a custom action that logs all traffic.SecRule...
- This is the rule that we created earlierSecResponseBodyAccess On
- Enables the ability to inspect and act upon the response body. This means ModSecurity will have access to and can analyze the content of the response body.SecRuleEngine On
- Turns on the ModSecurity rule engine, enabling the execution of ModSecurity rules.
SecAuditLog
directive. Then if you navigate to the `/admin` endpoint in the web browser, it should return the 403
unauthorized response. Then if you go into your audit log, you should see that rule match logged in there now.
With these steps, you can create your own custom configs and or rules to be used in coraza. You can also used the prebuilt OWASP Core Ruleset to cover attacks from the OWASP top 10.
Adding the OWASP Core Ruleset to Coraza:
Most of adding the core Ruleset to Coraza is documented here, so be sure to check out this documentation before proceeding.
- Download the required OWASP Core Rule Set files. I downloaded mine to the
/etc/caddy/rules
directory. You will need to download these files (I used wget to get them): - https://raw.githubusercontent.com/corazawaf/coraza/v2/master/coraza.conf-recommended
- This is the main conf for Coraza, the directives that we put in the caddyfile before can be added here. I renamed the file
/etc/caddy/rules/coraza.conf
- The actual rules file here https://coraza.io/docs/tutorials/coreruleset/
- This contains the rules (in the
/rules
directory) and the core ruleset conf filecrs-setup.conf.example
in the main directory when extracted (coreruleset) I copied the conf file, as is, to a file namedcre-setup.conf
- Just to be sure we have the items in the same location, the
coraza.conf
file is now in/etc/caddy/rules/coraza/conf
,crs-setup.conf
is now in/etc/caddy/rules/coreruleset/crs-setup.conf
, and the directory of rules is at/etc/caddy/rules/coreruleset/rules
. - Modify the Caddyfile to include the conf files and the rules files:
- You will need to include the configuration files inside of the directives for `coraza_waf`
{
order coraza_waf first
}
:80 {
coraza_waf {
load_owasp_crs
directives `
SecAction "id:1,pass,log"
Include /etc/caddy/rules/coreruleset/crs-setup.conf
Include /etc/caddy/rules/coraza.conf
Include /etc/caddy/rules/coreruleset/rules/*.conf
SecResponseBodyAccess On
SecRuleEngine On
`
}
# Set this path to your site's directory.
root * /var/www/html
# Enable the static file server.
file_server
}
coraza.conf
file. towards the bottom of it, there should be a section that has some default logging settings.
With all of this set up, you can restart caddy and it should all be running. You can test the WAF by adding something malicious.
The first thing I tried to test this was going to http://localhost/<script></script>
. This triggered the rule for XSS. and showed
that the WAF is working as expected.
Links That I Found Useful (Some of them were mentioned in the article):
- https://github.com/caddyserver/xcaddy
- https://coraza.io/docs/tutorials/coreruleset/
- https://medium.com/@jptosso/oss-waf-stack-using-coraza-caddy-and-elastic-3a715dcbf2f2
- https://caddyserver.com/docs/caddyfile/directives/log
- https://nature.berkeley.edu/~casterln/modsecurity/html-multipage/03-configuration-directives.html
- https://www.nginx.com/blog/modsecurity-logging-and-debugging/
- https://raw.githubusercontent.com/corazawaf/coraza/v2/master/coraza.conf-recommended
- https://coraza.io
- https://github.com/corazawaf/coraza-caddy/tree/main
- https://owasp.org/www-project-modsecurity-core-rule-set/
- https://coraza.io/docs/seclang/directives/
- https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v3.x)
- https://github.com/caddyserver/dist/blob/master/config/Caddyfile
- https://caddyserver.com/docs/caddyfile
- https://github.com/caddyserver/dist/blob/master/init/caddy.service