Getting Started With ModSecurity on Scalingo
ModSecurity is an open-source Web Application Firewall (WAF) that allows users to monitor, log and filter HTTP requests. A very common use case is to rely on a set of open-source rules provided by OWASP (called OWASP ModSecurity Core Rule Set (CRS)) to protect a web application against generic classes of vulnerabilities and Layer 7 attacks such as SQL injections, Cross Site Scripting, …
When enabled, ModSecurity analyzes every incoming HTTP request and compares the content of the request with a set of patterns. If there is a match, then it runs some actions, such as blocking the request, logging a message or returning a specific HTTP error.
ModSecurity implements its own Domain Specific Language, called SecLang, making it easy for users to write and share their own rules (combination of pattern and actions).
For this first tutorial, we use Nginx and the CRS provided by OWASP to deploy a WAF on Scalingo within minutes.
As a note, ModSecurity is only deployable on scalingo-20
and above.
Deploying Nginx
The very first steps consist of deploying an Nginx application on Scalingo. To do this, please follow the dedicated tutorial.
Enabling ModSecurity
Once your Nginx application is successfully deployed, set the environment
variable ENABLE_MODSECURITY
to true
, either by adding it via
your dashboard or by using the command line:
$ scalingo --app my-app env-set ENABLE_MODSECURITY=true
Trigger a new deployment of your application by creating an empty commit and pushing it to your Scalingo remote:
$ git commit --allow-empty -m "Enable ModSecurity"
$ git push scalingo master
The empty commit is detected by Scalingo and picked up to re-deploy the application. Several additional actions are done during this new deployment:
- ModSecurity and its dependencies are installed.
- Default configuration for ModSecurity is enabled. It downloads and enables the Core Rule Set (CRS) provided by OWASP.
Wait a few seconds for the deployment to finish and test that the CRS is actually active by issuing the following command:
$ curl -v -X INVALID_HTTP_METHOD https://my-app.osc-fr1.scalingo.io
The application should respond with a 403 forbidden such as the following:
> INVALID_HTTP_METHOD / HTTP/2
> Host: my-app.osc-fr1.scalingo.io
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 403
< date: Tue, 31 May 2022 13:58:46 GMT
< content-type: text/html; charset=utf-8
< content-length: 146
< x-request-id: 343e6a24-640e-499a-9dfe-f5dbb636ef45
< strict-transport-security: max-age=31536000
<
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>
Adding a Custom Rule
Now that we have a working WAF with a nice default set of rules, you may want to add your own custom rules. Let’s see how to do this!
First, create a file at the root of your Nginx application. In this example, we
call it my_rules.conf
, but you can choose whatever suits you.
Write your rules in this file, using SecLang and the SecRule
directive:
# This file is written in ModSecurity configuration language
# CUSTOM RULE id:1234
# IF query or body parameter contains a parameter named “param1” which contains “test”
# THEN block the request with a code 403 and log the event
SecRule ARGS:param1 "@contains test" \
"id:1234,\
deny,\
log,\
status:403,\
severity: 'CRITICAL',\
tag: 'custom-rule',\
msg: 'this is the log message you will see',\
logdata: '%{MATCHED_VAR_NAME}=%{MATCHED_VAR}'"
Once done, tell Nginx to load the brand-new rules. Edit the file you are using
for your Nginx configuration (i.g. nginx.conf
, nginx.conf.erb
, …) and add
two directives:
-
modsecurity
, which must be set toon
, and that enables ModSecurity. -
modsecurity_rules_file
, which must point to the file containing your rules. Since Scalingo deploys your app in/app/
, this path must start with/app/
.
Your file should end up like this:
# This file is written in Nginx configuration language
location / {
modsecurity on; # Enable ModSecurity on /
modsecurity_rules_file /app/my_rules.conf; # load custom rules file
# (...)
# The rest of your Nginx config file
}
You can now commit your changes and push them to your Scalingo remote, which triggers a new deployment of your WAF:
$ git add my_rules.conf
$ git add nginx.conf
$ git commit -m "Add modsecurity custom rules"
$ git push scalingo master
Disabling a Rule
If you want to disable a rule for some reason, you first need to identify the
rule, thanks to its id
number.
You can then add a SecRuleRemoveById
directive at the end of
your custom rules file, like this:
# This file is written in the ModSecurity config language
# Rule 911100 filters unknown HTTP methods, but we actually do want to allow exotic HTTP methods
SecRuleRemoveById 911100
Updating the Core Rule Set
Unless told otherwise (see below), Scalingo automatically downloads and uses
the latest stable version of the CRS when deploying your WAF. Consequently,
updating the CRS is just a git commit
and git push
effort:
$ git commit --allow-empty -m "Update Core Rule Set"
$ git push scalingo master
As always, pushing to your Scalingo remote triggers a new deployment of your application, and the new rules are downloaded.
Updating ModSecurity
Scalingo automatically deploys the latest version of ModSecurity we have packaged and tested. Consequently, updating ModSecurity only consists of triggering a new deployment of your WAF. To do so, you can create an empty commit and push it to Scalingo:
$ git commit --allow-empty -m "Update ModSecurity"
$ git push scalingo master
Customizing ModSecurity
Scalingo provides a few environment variables for you to tweak ModSecurity:
-
MODSECURITY_DEBUG_LOG_LEVEL
allows to specify the log level. It expects a value from0
(no logging) to9
(super verbose). Defaults to0
. -
MODSECURITY_AUDIT_LOG_LEVEL
allows to configure the audit logging engine. It can be set to eitherOn
(log all transactions),RelevantOnly
(only log transactions that returned with a status code of 4xx or 5xx) orOff
(do not log transactions). Defaults toOff
.