ModSecurity with Nginx on CentOS 8
Tutorial on how to configure ModSecurity with Nginx on CentOS 8
ModSecurity, sometimes called Modsec, is an open-source web application firewall (WAF) to provide protections against generic classes of vulnerabilities using the OWASP ModSecurity Core Rule Set (CRS)
Preparation of CentOS 8
- Install a fresh copy of CentOS 8 with minimal install in VMware ESXi Host
- Perform System Update
dnf check-update
dnf update
## Free up disk space by deleting all downloaded software package and all cached repository
dnf clean all
- Install required build tools and dependencies
dnf install gcc-c++ flex bison yajl curl-devel curl zlib-devel pcre-devel autoconf automake git curl make libxml2-devel pkgconfig libtool httpd-devel redhat-rpm-config git wget openssl openssl-devel vim nano
- Install EPEL & Remi Repository
dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf --enablerepo=PowerTools install doxygen yajl-devel
- Install GeoIP Module to allows system administrators to redirect or block web traffic according to the client’s geographical location
dnf --enablerepo=remi install GeoIP-devel
Install ModSecurity
- Clone the latest LibModsecurity GitHub repository to /opt/modsec
mkdir /opt/modsec
cd /opt/modsec
git clone --depth 1 -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity
- Compile and install ModSecurity
cd ModSecurity
git submodule init
git submodule update
./build.sh
./configure
make
make install
Compile Nginx to support ModSecurity
- Download the ModSecurity-nginx connector which provides a communication channel between Nginx and LibModsecurity
cd /opt/modsec
git clone https://github.com/SpiderLabs/ModSecurity-nginx.git
- Create a non-privileged system user and group for Nginx
useradd -r -M -s /sbin/nologin -d /usr/local/nginx nginx
- Download Nginx and extract it to /opt/nginx
cd /opt
wget http://nginx.org/download/nginx-1.17.6.tar.gz
tar xzf nginx-1.17.6.tar.gz
- Compile Nginx from source code
cd nginx-1.17.6
./configure --user=nginx --group=nginx --with-pcre-jit --with-debug --with-http_ssl_module --with-http_v2_module --with-http_realip_module --add-module=/opt/modsec/ModSecurity-nginx
make
make install
- Create a systemd service for Nginx
nano /etc/systemd/system/nginx.service
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=mixed
PrivateTmp=true
[Install]
WantedBy=multi-user.target
- Create a symbolic link of Nginx binary to
/usr/sbin/
path.
ln -s /usr/local/nginx/sbin/nginx /usr/sbin/
- Change the location of pid to /run/nginx.pid in Nginx config file
nano /usr/local/nginx/conf/nginx.conf
pid /run/nginx.pid;
- Enable and start Nginx service
systemctl enable --now nginx
- Allow Firewalld for HTTP & HTTPS
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
# Restart Firewalld
systemctl restart firewalld.service
# Verify HTTP & HTTPS is allowed
firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: ens192
sources:
services: cockpit dhcpv6-client http https ssh
ports:
- You should be able to access to Nginx Web Server via http://IP-ADDRESS now
Let’s Encrypt Wildcard SSL Certificate
Generate Let’s Encrypt wildcard SSL Certificate with certbot with DNS Plugin for DnsMadeEasy for *.aventis.dev
#Install Certbot
sudo dnf install certbot
#Install DNS Plugin
sudo dnf install python3-certbot-dns-dnsmadeeasy
Obtain the API & Secret Key from DnsMadeEasy and save it to ~/.secrets/dnsmadeeasy.ini
mkdir ~/.secrets
nano ~/.secrets/dnsmadeeasy.ini
# DNS Made Easy API credentials used by Certbot
dns_dnsmadeeasy_api_key = XXXXXXXXXXXXXXXXXXXXXXXXX
dns_dnsmadeeasy_secret_key = XXXXXXXXXXXXXXXXXXXXXX
Request the SSL Certificate from Let’s Encrypt
[root@centos /]# certbot certonly \
> --dns-dnsmadeeasy \
> --dns-dnsmadeeasy-credentials ~/.secrets/dnsmadeeasy.ini \
> -d *.aventis.dev
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator dns-dnsmadeeasy, Installer None
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel): [email protected]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: A
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for aventis.dev
Waiting 60 seconds for DNS changes to propagate
Waiting for verification...
Cleaning up challenges
Subscribe to the EFF mailing list (email: [email protected]).
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/aventis.dev/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/aventis.dev/privkey.pem
Your cert will expire on 2020-12-06. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
Let’s Encrypt wildcard SSL Certificate is stored in /etc/letsencrypt/live/aventis.dev
[root@centos /]# ls /etc/letsencrypt/live/aventis.dev
cert.pem chain.pem fullchain.pem privkey.pem README
Enable SSL & HTTP/2 Support
Copy the cert.pem & privkey.pem to /usr/local/nginx/ssl/
mkdir /usr/local/nginx/ssl
cp /etc/letsencrypt/live/aventis.dev/cert.pem /usr/local/nginx/ssl/
cp /etc/letsencrypt/live/aventis.dev/privkey.pem /usr/local/nginx/ssl/
Enable SSL Support in /usr/local/nginx/conf/nginx.conf
nano /usr/local/nginx/conf/nginx.conf
server {
#Enable SSL
listen 443 ssl http2;
server_name localhost uat.aventis.dev;
#enable SSL
ssl_certificate /usr/local/nginx/ssl/cert.pem;
ssl_certificate_key /usr/local/nginx/ssl/privkey.pem;
}
Verify the configuration file of Nginx and restart the service
nginx -t
systemctl restart nginx
Go to https://uat.aventis.dev and verify that HTTP/2 is enabled with Google Chrome – Developer Tool
PHP-FPM with NGINX
PHP 7.2 is the default installation profiles
$ sudo dnf module list php
Last metadata expiration check: 0:48:01 ago on Mon 07 Sep 2020 03:54:30 PM +08.
CentOS-8 - AppStream
Name Stream Profiles Summary
php 7.2 [d] common [d], devel, minimal PHP scripting language
php 7.3 common [d], devel, minimal PHP scripting language
Remi's Modular repository for Enterprise Linux 8 - x86_64
Name Stream Profiles Summary
php remi-7.2 common [d], devel, minimal PHP scripting language
php remi-7.3 common [d], devel, minimal PHP scripting language
php remi-7.4 [e] common [d], devel, minimal PHP scripting language
Install the latest PHP 7.4 Module
$ sudo dnf module reset php
#Enable PHP 7.4
$ sudo dnf module enable php:remi-7.4
#Install PHP 7.4
$ sudo dnf install php php-opcache php-gd php-curl php-mysqlnd
#Verify the PHP version installed
[kwyong@centos ~]$ php -v
PHP 7.4.10 (cli) (built: Sep 1 2020 13:58:08) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.10, Copyright (c), by Zend Technologies
Change the PHP-FPM to use nginx instead of Apache user in
$ nano /etc/php-fpm.d/www.conf
user = nginx
group = nginx
Enable and start PHP-FPM
$ sudo systemctl enable php-fpm
$ sudo systemctl start php-fpm
Configure PHP-FPM with Nginx by modifying /usr/local/nginx/conf/nginx.conf
$ sudo nano /usr/local/nginx/conf/nginx.conf
location ~ \.php$ {
root html;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Restart Nginx & PHP-FPM services
$ sudo systemctl restart nginx
$ sudo systemctl restart php-fpm
Create info.php in /usr/local/nginx/html (Default Root Directory for Nginx)
$ nano /usr/local/nginx/html/info.php
<?php
phpinfo();
?>
Go to https://uat.aventis.dev/info.php to verify PHP is installed successfully
Confgure Nginx with ModSecurity
- Copy the Sample ModSecurity config file and unicode.mapping from ModSecurity source directory to Nginx configuration directory
cp /opt/modsec/ModSecurity/modsecurity.conf-recommended /usr/local/nginx/conf/modsecurity.conf
cp /opt/modsec/ModSecurity/unicode.mapping /usr/local/nginx/conf/
- Create a backup for Nginx config file
cp /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.conf.bak
- Create /var/log/nginx to keep all the access & error log
mkdir /var/log/nginx
- Add the following to the nginx.conf file
nano /usr/local/nginx/conf/nginx.conf
user nginx;
http {
server {
modsecurity on;
modsecurity_rules_file /usr/local/nginx/conf/modsecurity.conf;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}
- Restart Nginx service
systemctl restart nginx
By default, ModSecurity is set on detection only mode where it only logs the requests based on the triggered rules without blocking anything
- Turn On ModSecurity Rule Engine
nano /usr/local/nginx/conf/modsecurity.conf
- Change the default log directory for ModSecurity to /var/log/nginx/modsec_audit.log
# Use a single file for logging. This is much easier to look at, but
# assumes that you will use the audit log only ocassionally.
SecAuditLogType Serial
SecAuditLog /var/log/nginx/modsec_audit.log
Install OWASP ModSecurity Core Rule Set (CRS)
The OWASP ModSecurity Core Rule Set (CRS) is a set of generic attack detection rules for use with ModSecurity. It aims at protecting the web applications from a wide range of attacks, including the OWASP Top Ten, minimum of false alerts.
- Clone the CRS from GitHub Repository to /usr/local/nginx/conf
git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git /usr/local/nginx/conf/owasp-crs
- Rename the crs-setup.conf.example to crs-setup.conf
cp /usr/local/nginx/conf/owasp-crs/crs-setup.conf.example /usr/local/nginx/conf/owasp-crs/crs-setup.conf
- Include the OWASP rule to /usr/local/nginx/conf/modsecurity.conf
nano /usr/local/nginx/conf/modsecurity.conf
Include owasp-crs/crs-setup.conf
Include owasp-crs/rules/*.conf
- Verify Nginx configuration file and restart Nginx service
nginx -t
systemctl restart nginx
Testing ModSecurity on Nginx
Run the following command injection
[root@prod-centos conf]# curl localhost/index.html?exec=/bin/bash
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.17.6</center>
</body>
</html>
Check the log file
[root@prod-centos conf]# tail /var/log/nginx/modsec_audit.log
ModSecurity: Warning. Matched "Operator `Rx' with parameter `^[\d.:]+$' against variable `REQUEST_HEADERS:Host' (Value: `192.168.1.236' ) [file "/usr/local/nginx/conf/owasp-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "722"] [id "920350"] [rev ""] [msg "Host header is a numeric IP address"] [data "192.168.1.236"] [severity "4"] [ver "OWASP_CRS/3.2.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "OWASP_CRS/PROTOCOL_VIOLATION/IP_HOST"] [tag "WASCTC/WASC-21"] [tag "OWASP_TOP_10/A7"] [tag "PCI/6.5.10"] [hostname "192.168.1.236"] [uri "/index.html"] [unique_id "1591167373"] [ref "o0,13v46,13"]
ModSecurity: Warning. Matched "Operator `PmFromFile' with parameter `unix-shell.data' against variable `ARGS:exec' (Value: `/bin/bash' ) [file "/usr/local/nginx/conf/owasp-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "496"] [id "932160"] [rev ""] [msg "Remote Command Execution: Unix Shell Code Found"] [data "Matched Data: bin/bash found within ARGS:exec: /bin/bash"] [severity "2"] [ver "OWASP_CRS/3.2.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-shell"] [tag "platform-unix"] [tag "attack-rce"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "OWASP_CRS/WEB_ATTACK/COMMAND_INJECTION"] [tag "WASCTC/WASC-31"] [tag "OWASP_TOP_10/A1"] [tag "PCI/6.5.2"] [hostname "192.168.1.236"] [uri "/index.html"] [unique_id "1591167373"] [ref "o1,8v21,9t:urlDecodeUni,t:cmdLine,t:normalizePath,t:lowercase"]
ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `8' ) [file "/usr/local/nginx/conf/owasp-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "80"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 8)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.2.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "192.168.1.236"] [uri "/index.html"] [unique_id "1591167373"] [ref ""]