How To Set Up ModSecurity With Nginx On Debian
How To Set Up ModSecurity With Nginx On Debian
Nginx on Debian/Ubuntu
Debian, Security, Ubuntu
This tutorial is going to show you how to install and use ModSecurity with
Nginx on Debian/Ubuntu servers. ModSecurity is the most well-known open-
source web application firewall (WAF), providing comprehensive
protection for your web applications (like WordPress, Nextcloud, Ghost etc)
against a wide range of Layer 7 (HTTP) attacks, such as SQL injection, cross-
site scripting, and local file inclusion.
Note: This tutorial works on all current Debian and Ubuntu versions,
including Debian 11, Ubuntu 18.04, Ubuntu 20.04 and Ubuntu 22.04.
Web applications are inherently insecure. If you are a WordPress admin, you
will probably hear news of hackers exploiting vulnerabilities in WordPress
plugins and themes every once in a while. It’s essential that you deploy a
WAF on your web server, especially when you have old applications that
don’t receive updates. ModSecurity is originally created by Ivan Ristić in
2002, currently maintained by Trustwave SpiderLabs. It’s the world’s most
widely deployed WAF, used by over a million websites. cPanel, the most
widely used hosting control panel, includes ModSecurity as its WAF.
You may have heard other host-based firewalls like iptables, UFW, and
Firewalld, etc. The difference is that they work on layer 3 and 4 of the OSI
model and take actions based on IP address and port number. ModSecurity,
or web application firewalls in general, is specialized to focus on HTTP traffic
(layer 7 of the OSI model) and takes action based on the content of HTTP
request and response.
ModSecurity 3
ModSecurity was originally designed for Apache web server. It could work
with Nginx before version 3.0 but suffered from poor performance.
ModSecurity 3.0 (aka libmodsecurity) was released in 2017. It’s a
milestone release, particularly for Nginx users, as it’s the first version to
work natively with Nginx. The caveat of ModSecurity 3 is that it doesn’t yet
have all the features as in the previous version (2.9), though each new
release will add some of the missing features.
All Nginx binaries in the PPA are compiled with the --with-compat argument.
Ubuntu
Debian 11
Save and close the file. Then update the repository index.
mkdir -p /usr/local/src/nginx
cd /usr/local/src/nginx/
If you see the following warning message, you can safely ignore it.
ls
Sample output:
nginx-1.19.5
nginx_1.19.5-3+ubuntu20.04.1+deb.sury.org+1.debian.tar.xz
nginx_1.19.5-3+ubuntu20.04.1+deb.sury.org+1.dsc
nginx_1.19.5.orig.tar.gz
Make sure the source code version is the same as your Nginx binary version
(sudo nginx -v).
cd /usr/local/src/ModSecurity/
./build.sh
./configure
Compile the source code with the following command. By default, make will
use only one CPU core. I have 4 CPU cores on my server, so I specify 4 jobs
(-j4) for make to speed up the compilation process. Change 4 to your own
number of CPU cores.
make -j4
After the make command finished without errors, install the binary.
error #1
If you see the following error when running the make command, it’s probably
because your server is out of RAM.
error #2
make clean
./configure
make -j4
Install the binary.
cd /usr/local/src/nginx/nginx-1.26.1/
Next, configure the environment with the following command. We will not
compile Nginx itself, but compile the ModSecurity Nginx
Connector module only. The --with-compat flag will make the module
binary-compatible with your existing Nginx binary.
sudo cp objs/ngx_http_modsecurity_module.so
/usr/share/nginx/modules/
load_module modules/ngx_http_modsecurity_module.so;
Also, add the following two lines in the http {...} section, so ModSecurity
will be enabled for all Nginx virtual hosts.
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
Save and close the file. Next, create the /etc/nginx/modsec/ directory to
store ModSecurity configuration
sudo cp /usr/local/src/ModSecurity/modsecurity.conf-
recommended /etc/nginx/modsec/modsecurity.conf
SecRuleEngine DetectionOnly
This config tells ModSecurity to log HTTP transactions, but takes no action
when an attack is detected. Change it to the following, so ModSecurity will
detect and block web attacks.
SecRuleEngine On
Then find the following line (line 224), which tells ModSecurity what
information should be included in the audit log.
SecAuditLogParts ABIJDEFHZ
However, the default setting is wrong. You will know why later when I explain
how to understand ModSecurity logs. The setting should be changed to the
following.
SecAuditLogParts ABCEFHJKZ
If you have a coding website, you might want to disable response body
inspection, otherwise, you might get 403 forbidden errors just by loading a
web page with lots of code content.
SecResponseBodyAccess Off
Save and close the file. Next, create the /etc/nginx/modsec/main.conf file.
Include /etc/nginx/modsec/modsecurity.conf
Save and close the file. We also need to copy the Unicode mapping file.
sudo cp /usr/local/src/ModSecurity/unicode.mapping
/etc/nginx/modsec/
sudo nginx -t
It’s free, community-maintained and the most widely used rule set that
provides a sold default configuration for ModSecurity.
It contains rules to help stop common attack vectors, including SQL
injection (SQLi), cross-site scripting (XSS), and many others.
It can integrate with Project Honeypot.
It also contains rules to detect bots and scanners.
It has been tuned through wide exposure to have very few false
positives.
wget
https://github.com/coreruleset/coreruleset/archive/v3.3.5.tar.gz
sudo mv /etc/nginx/modsec/coreruleset-3.3.5/crs-
setup.conf.example /etc/nginx/modsec/coreruleset-3.3.5/crs-
setup.conf
Include /etc/nginx/modsec/coreruleset-3.3.5/crs-setup.conf
Include /etc/nginx/modsec/coreruleset-3.3.5/rules/*.conf
sudo nginx -t
You can see that OWASP CRS can run in two modes:
self-contained mode. This is the traditional mode used in CRS v2.x. If
an HTTP request matches a rule, ModSecurity will block the HTTP
request immediately and stop evaluating remaining rules.
anomaly scoring mode. This is the default mode used in CRS v3.x.
ModSecurity will check an HTTP request against all rules, and add a
score to each matching rule. If a threshold is reached, then the HTTP
request is considered an attack and will be blocked. The default score
for inbound requests is 5 and for outbound response is 4.
With each paranoia level increase, the CRS enables additional rules giving
you a higher level of security. However, higher paranoia levels also increase
the possibility of blocking some legitimate traffic due to false alarms.
These are the two basic concepts you need to understand before using the
CRS. Now we can close the file. The individual CRS rules are stored
in /etc/nginx/modsec/coreruleset-3.3.5/rules/ directory. Each matching
rule will increase the anomaly score.
Step 8: Testing
To check if ModSecurity is working, you can launch a simple SQL injection
attack on your own website. (Please note that it’s illegal to do security
testing on other people’s websites without authorization.) Enter the followign
URL in your web browser.
https://tanjungsari-rembang.desa.id/?id=3 or 'a'='a'
If ModSecurity is working properly, your Nginx web server should return a
403 forbidden error message.
Firefox browser may fail to show the 403 error message. You can
press Ctrl+Shift+I to open the developer tools window and select
the network tab. Then press F5 to reload the web page. You should now see
the 403 error code in Firefox.
If you run a high-traffic website, the ModSecurity audit log can get too large
very quickly, so we need to configure log rotation for the ModSecurity audit
log. Create a logrotate configuration file for ModSecurity.
/var/log/modsec_audit.log
{
rotate 14
daily
missingok
compress
delaycompress
notifempty
}
This will rotate the log file every day (daily), compressing old versions
(compress). The previous 14 log files will be kept ( rotate 14), and no rotation
will occur if the file is empty (notifempty). Save and close the file.
For example, by default, the CRS forbids Unix command injection like
entering sudo on a web page, which is rather common on my blog. To
eliminate false positives, you need to add rule exclusions to the CRS.
#SecAction \
# "id:900130,\
# phase:1,\
# nolog,\
# pass,\
# t:none,\
# setvar:tx.crs_exclusions_cpanel=1,\
# setvar:tx.crs_exclusions_drupal=1,\
# setvar:tx.crs_exclusions_dokuwiki=1,\
# setvar:tx.crs_exclusions_nextcloud=1,\
# setvar:tx.crs_exclusions_wordpress=1,\
# setvar:tx.crs_exclusions_xenforo=1"
For instance, If I want to enable WordPress exclusions, the above lines should
be changed to the following. Please be careful about the syntax. There
should be no comments
between t:none,\ and setvar:tx.crs_exclusions_wordpress=1". (The
backslash \ character at the end indicates the next line is a continuation of
the current line.)
SecAction \
"id:900130,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.crs_exclusions_wordpress=1"
# setvar:tx.crs_exclusions_cpanel=1,\
# setvar:tx.crs_exclusions_drupal=1,\
# setvar:tx.crs_exclusions_dokuwiki=1,\
# setvar:tx.crs_exclusions_nextcloud=1,\
# setvar:tx.crs_exclusions_xenforo=1"
sudo nginx -t
If the test is successful, reload Nginx for the change to take effect.
cd /etc/nginx/modsec/coreruleset-3.3.4/rules
sudo mv REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example
REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
Add the following line at the bottom of this file. If your WordPress is using
the blog.yourdomain.com sub-domain and the request header send from
visitor’s browser contains this sub-domain, then ModSecurity will apply the
rule exclusions for WordPress.
If you have installed Nextcloud on the same server, then you can also add
the following line in this file, so if a visitor is accessing your Nextcloud sub-
domain, ModSecurity will apply the Nextcloud rule exclusions.
sudo nginx -t
If the test is successful, reload Nginx for the change to take effect.
Section H in the audit log tells you which rule is matched. For example, If I
try to use the <code>...</code> HTML in the comment form, ModSecurity
blocks my comment. The following log tell me that the HTTP request
matched a rule in REQUEST-941-APPLICATION-ATTACK-XSS.conf (line 527).
The rule ID is 941310. The request URI is /wp-comments-post.php.
It’s detected as malformed encoding XSS filter attack. However, I want users
to be able to use the <code>...</code> and <pre>...</pre> HTML tag in the
comment form, so I created a rule exclusion. Add the following line at the
bottom of the REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf file.
sudo nginx -t
If a false positive matches multiple rule IDs, you can add rule exclusions in
one line like so:
IP Whitelisting
If you want to disable ModSecurity for your own IP address, but leave it
enabled for all other IP addresses, then add the following custom rule in
the REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf file.
Replace 12.34.56.78 with your real IP address.
sudo nginx -t
If the test is successful, restart Nginx for the change to take effect.
Chaining Rules
If your Nginx has multiple virtual hosts, you may want to whitelist your IP
address for a specific virtual host. You need to chain two rules like so:
The chain keyword at the end of the first rule indicates that
the ruleEngine=off action will only be taken if the condition in the next rule
is also true.
Note that using Project Honeypot will make your website slower for new
visitors, because your web server will need to send a query to Project
Honeypot before it can send a response to the new visitor. However, once
the IP reputation data is cached on your web server, the performance impact
will be very minimal.
To use Project Honeypot, first create a free account on its website. Then go
to your account dashboard and click the get one link to request an access
key for the HTTP blacklist.
Next, edit the crs-setup.conf file.
#SecHttpBlKey XXXXXXXXXXXXXXXXX
#SecAction "id:900500,\
# phase:1,\
# nolog,\
# pass,\
# t:none,\
# setvar:tx.block_search_ip=1,\
# setvar:tx.block_suspicious_ip=1,\
# setvar:tx.block_harvester_ip=1,\
# setvar:tx.block_spammer_ip=1"
Now ModSecurity will query Project Honeypot on all HTTP requests. To test if
this would work, edit the /etc/nginx/modsec/main.conf file.
Add the following line at the end of this file. This allows us to pass an IP
address in an URL. (Once the test is successful, you can remove this line
from the file.)
Your web server should return a 403 forbidden response, because the IP
address on Project Honeypot.
brotli on;
brotli_comp_level 6;
brotli_static on;
brotli_types application/atom+xml application/javascript
application/json application/rss+xml
application/vnd.ms-fontobject application/x-font-
opentype application/x-font-truetype
application/x-font-ttf application/x-javascript
application/xhtml+xml application/xml
font/eot font/opentype font/otf font/truetype
image/svg+xml image/vnd.microsoft.icon
image/x-icon image/x-win-bitmap text/css
text/javascript text/plain text/xml;
sudo nginx -t
Now go to the home page your website, open the developer tools in your
web browser (In Firefox you can press Ctrl+Alt+I to open it up). Select
the Network tab, and press F5 to reload the web page. Click on the main
HTML page.
Check the response header on the right sidebar. If the content-encoding is
set to br, then you have successfully enabled Brotli compression in Nginx.
If the content-encoding is gzip, then your Nginx web server is still using
GZIP.
Upgrading Nginx
ModSecurity integrates with Nginx as a dynamic module, so every time the
Nginx binary is upgraded, you need to rebuild the ModSecurity module for
Nginx. This will make your application offline for a few minutes.
My advice is to prevent Nginx from being upgraded when you run sudo apt
upgrade command. This can be achieved by the following command:
Now if you run sudo apt update;sudo apt upgrade, and the package
manager tells you that the nginx package is held back from upgrading, then
it means there’s a new nginx version available in the repository.
You should download the new Nginx source package and compile the
ModSecurity module again. Move the newly-compiled ModSecurity module
to /usr/share/nginx/modules/ directory. Basically that means you need to
remove everything under /usr/local/src/ directory (sudo rm
/usr/local/src/* -rf ) and go through step 2 and step 4 again.
Then unhold Nginx.
apt-mark showhold
Nginx Plus
If you use the commercial Nginx Plus web server, then ModSecurity is
included in the Nginx Plus binary. It’s known as the NGINX WAF.
If you don’t want to spend time re-compiling the ModSecurity source code,
then you might want to purchase Nginx Plus, as the ModSecurity module is
pre-compiled in the Nginx Plus binary. Benefits of Using ModSecurity 3.0 with
NGINX Plus:
modsecurity on;
This will enable ModSecurity for all Nginx server blocks (aka virtual hosts). If
you want to disable ModSecurity for a specific server block, then edit the
server block file (/etc/nginx/conf.d/example.com.conf) and add the
following line to the server {...} context.
modsecurity off;
FAQ
Static Module vs Dynamic Module in Nginx
A static module must be compiled with Nginx and it’s integrated with
Nginx as one binary. It can’t be unloaded from Nginx.
A dynamic module is a separate package from the main Nginx binary.
It can be loaded and unloaded in Nginx.
You need to restart Nginx and upgrade server RAM, then the above error is
not going to happen again.
Go through step 6 again to install the new version of core rule set.
Then go to step 10. Copy of your custom rules in the crs-setup.conf
and REQUEST-900-EXCLUSION-RULES-BEFORE-CRS file.
sudo nginx -t
If the test is successful, reload Nginx for the change to take effect.
How do you know if the new version is working? Launch a simple SQL
injection attack like in step 8 and check your server logs. It will show you the
CRS version that’s preventing this attack.