Setting Up ModSecurity on Ubuntu
Recently, I am experimenting with Web Application Firewalls a lot. ModSecurity is one of them. It is the most famous and useful open-source Web Application Firewall (WAF) in existence. It is supported by various web servers such as Apache, Nginx, and IIS.
The job of ModSecurity is to sit in front of the application web server and check the incoming requests and outgoing responses to filter out malicious content. It does so by the use of powerful and complex regular expressions. ModSecurity uses a rule language for its rules. The rule language has variables and operators defined to aid in the process of parsing HTTP requests.
ModSecurity, in itself, cannot block or allow requests. It is just a rule engine. It requires rules to operate appropriately. That's where its sister project, Core Rule Set (CRS), comes into the picture. CRS is a rule set developed to be used with ModSecurity. It has been in active development for several years now and is very mature. Together, ModSecurity and CRS form a formidable defense against the widespread web application attacks.
Now that you know, what a WAF is, let's proceed to install ModSecurity on Ubuntu. I will be compiling ModSecurity's latest version on Ubuntu 18.04. We will also configure ModSecurity to use Core Rule Set.
Installing Dependencies
ModSecurity requires some dependencies to work correctly. Let's install them -
First, upgrade the Ubuntu system.
sudo apt-get -y update
sudo apt-get -y upgrade
sudo apt-get -y update
sudo apt-get -y upgrade
Now install the dependencies.
sudo apt-get -y install git libtool dh-autoreconf pkgconf gawk libcurl4-gnutls-dev libexpat1-dev libpcre3-dev libssl-dev libxml2-dev libyajl-dev zlibc zlib1g-dev libxml2 libpcre++-dev libxml2-dev libgeoip-dev liblmdb-dev lua5.2-dev iputils-ping locales apache2 apache2-dev ca-certificates wget
sudo apt-get -y install git libtool dh-autoreconf pkgconf gawk libcurl4-gnutls-dev libexpat1-dev libpcre3-dev libssl-dev libxml2-dev libyajl-dev zlibc zlib1g-dev libxml2 libpcre++-dev libxml2-dev libgeoip-dev liblmdb-dev lua5.2-dev iputils-ping locales apache2 apache2-dev ca-certificates wget
Optional: clean up the Ubuntu caches.
sudo apt-get clean && sudo rm -rf /var/lib/apt/lists/*
sudo apt-get clean && sudo rm -rf /var/lib/apt/lists/*
Install SSDeep
SSDeep
as well (as done
here )
cd ~
git clone https://github.com/ssdeep-project/ssdeep
cd ssdeep
./bootstrap
./configure
make
sudo make install
cd ~
git clone https://github.com/ssdeep-project/ssdeep
cd ssdeep
./bootstrap
./configure
make
sudo make install
Compiling ModSecurity
Let's clone ModSecurity from Github.
cd ~
git clone -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity
cd ModSecurity
git submodule init
git submodule update
./build.sh
./configure
make # takes ~8 minutes on AWS t2.micro
sudo make install
cd ~
git clone -b v3/master --single-branch https://github.com/SpiderLabs/ModSecurity
cd ModSecurity
git submodule init
git submodule update
./build.sh
./configure
make # takes ~8 minutes on AWS t2.micro
sudo make install
Compiling ModSecurity-apache connector
To configure it with Apache, we will require ModSecurity-apache connector. Let's install that as well.
cd ~
git clone https://github.com/SpiderLabs/ModSecurity-apache
cd ModSecurity-apache
./autogen.sh
./configure --with-libmodsecurity=/usr/local/modsecurity
make
sudo make install
cd ~
git clone https://github.com/SpiderLabs/ModSecurity-apache
cd ModSecurity-apache
./autogen.sh
./configure --with-libmodsecurity=/usr/local/modsecurity
make
sudo make install
Setting up CRS rules
Now, let's download CRS rule set as well.
cd ~
git clone -b v3.2/dev https://github.com/SpiderLabs/owasp-modsecurity-crs
sudo mv owasp-modsecurity-crs/ /usr/local/
cd ~
git clone -b v3.2/dev https://github.com/SpiderLabs/owasp-modsecurity-crs
sudo mv owasp-modsecurity-crs/ /usr/local/
Rename CRS configuration file -
sudo mv /usr/local/owasp-modsecurity-crs/crs-setup.conf.example /usr/local/owasp-modsecurity-crs/crs-setup.conf
sudo mv /usr/local/owasp-modsecurity-crs/crs-setup.conf.example /usr/local/owasp-modsecurity-crs/crs-setup.conf
Setting up ModSecurity
Now, we need to create a file in the Apache modules directory, so that Apache can know, how to activate ModSecurity.
Create /etc/apache2/mods-enabled/security3.conf
/etc/apache2/mods-enabled/security3.conf
file and paste the following
contents -
LoadModule security3_module /usr/lib/apache2/modules/mod_security3.so
modsecurity on
modsecurity_rules_file '/etc/apache2/modsec/main.conf'
LoadModule security3_module /usr/lib/apache2/modules/mod_security3.so
modsecurity on
modsecurity_rules_file '/etc/apache2/modsec/main.conf'
As you can see, the last line in the above code block reference a file
main.conf
main.conf
in a folder modsec
modsec
. This folder will not be present by default. We
need to create that.
sudo mkdir -p /etc/apache2/modsec
sudo mkdir -p /etc/apache2/modsec
Setup ModSecurity configuration file -
# enables Unicode support in ModSecurity
sudo wget -P /etc/apache2/modsec/ https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/unicode.mapping
sudo wget -P /etc/apache2/modsec/ https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
sudo mv /etc/apache2/modsec/modsecurity.conf-recommended /etc/apache2/modsec/modsecurity.conf
# enables Unicode support in ModSecurity
sudo wget -P /etc/apache2/modsec/ https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/unicode.mapping
sudo wget -P /etc/apache2/modsec/ https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
sudo mv /etc/apache2/modsec/modsecurity.conf-recommended /etc/apache2/modsec/modsecurity.conf
Change the SecRuleEngine directive in the configuration to change from the default "detection only" mode to actively dropping malicious traffic.
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/apache2/modsec/modsecurity.conf
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/apache2/modsec/modsecurity.conf
Change the location of modsec_audit.log
modsec_audit.log
file to Apache log directory.
sudo sed -i 's/SecAuditLog \/var\/log\/modsec_audit.log/SecAuditLog \/var\/log\/apache2\/modsec_audit.log/' /etc/apache2/modsec/modsecurity.conf
sudo sed -i 's/SecAuditLog \/var\/log\/modsec_audit.log/SecAuditLog \/var\/log\/apache2\/modsec_audit.log/' /etc/apache2/modsec/modsecurity.conf
To configure ModSecurity to use CRS rule set, put the following text in
/etc/apache2/modsec/main.conf
/etc/apache2/modsec/main.conf
file.
Include "/etc/apache2/modsec/modsecurity.conf"
Include "/usr/local/owasp-modsecurity-crs/crs-setup.conf"
Include "/usr/local/owasp-modsecurity-crs/rules/*.conf"
Include "/etc/apache2/modsec/modsecurity.conf"
Include "/usr/local/owasp-modsecurity-crs/crs-setup.conf"
Include "/usr/local/owasp-modsecurity-crs/rules/*.conf"
Also enable some Apache modules for better functioning of ModSecurity.
sudo a2enmod unique_id headers rewrite actions dav dav_fs
sudo a2enmod unique_id headers rewrite actions dav dav_fs
Now restart the Apache server
sudo systemctl restart apache2
sudo systemctl restart apache2
Fixing some common issues
Sometimes, I had encountered errors when ModSecurity was not able to append logs to its log file. I figured out that ModSecurity did not have enough permissions to write that file. We can fix this issue quickly.
First, test if you really have this issue or not.
$ curl 'http://localhost/?q=">
<script>
alert(1)
</script>
'
<!DOCTYPE html PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<h1>Forbidden</h1>
<p>You dont have permission to access / on this server.<br /></p>
<hr />
<address>Apache/2.4.29 (Ubuntu) Server at localhost Port 80</address>
</body>
</html>
$ curl 'http://localhost/?q=">
<script>
alert(1)
</script>
'
<!DOCTYPE html PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<h1>Forbidden</h1>
<p>You dont have permission to access / on this server.<br /></p>
<hr />
<address>Apache/2.4.29 (Ubuntu) Server at localhost Port 80</address>
</body>
</html>
Now go to Apache log directory and check the contents of modsec_audit.log
modsec_audit.log
file.
cd /var/log/apache2
tail modsec_audit.log
cd /var/log/apache2
tail modsec_audit.log
You should see the following content -
---0LzdyETA---A--
[01/Jul/2019:14:42:41 +0000] 156199216179.666171 127.0.0.1 41824 ip-xxx-xx-xx-xx.ap-south-1.compute.internal 80
---0LzdyETA---B--
GET /?q="><script>alert(1)</script> HTTP/1.1
Host: localhost
User-Agent: curl/7.58.0
Accept: */*
---TqjMwy7h---D--
---TqjMwy7h---F--
HTTP/1.1 403
---TqjMwy7h---H--
ModSecurity: Warning. detected XSS using libinjection. [file "/usr/local/owasp-modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"] [line "37"] [id "941100"] [rev ""] [msg "XSS Attack Detected via libinjection"] [data "Matched Data: XSS data found within ARGS:q: "><script>alert(1)</script>"] [severity "2"] [ver "OWASP_CRS/3.1.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-xss"] [tag "OWASP_CRS/WEB_ATTACK/XSS"] [tag "WASCTC/WASC-8"] [tag "WASCTC/WASC-22"] [tag "OWASP_TOP_10/A3"] [tag "OWASP_AppSensor/IE1"] [tag "CAPEC-242"] [hostname "localhost"] [uri "/"] [unique_id "156198848361.198287"] [ref "v8,27t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls"]
....
....
---TqjMwy7h---I--
---TqjMwy7h---J--
---TqjMwy7h---Z--
---0LzdyETA---A--
[01/Jul/2019:14:42:41 +0000] 156199216179.666171 127.0.0.1 41824 ip-xxx-xx-xx-xx.ap-south-1.compute.internal 80
---0LzdyETA---B--
GET /?q="><script>alert(1)</script> HTTP/1.1
Host: localhost
User-Agent: curl/7.58.0
Accept: */*
---TqjMwy7h---D--
---TqjMwy7h---F--
HTTP/1.1 403
---TqjMwy7h---H--
ModSecurity: Warning. detected XSS using libinjection. [file "/usr/local/owasp-modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"] [line "37"] [id "941100"] [rev ""] [msg "XSS Attack Detected via libinjection"] [data "Matched Data: XSS data found within ARGS:q: "><script>alert(1)</script>"] [severity "2"] [ver "OWASP_CRS/3.1.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-xss"] [tag "OWASP_CRS/WEB_ATTACK/XSS"] [tag "WASCTC/WASC-8"] [tag "WASCTC/WASC-22"] [tag "OWASP_TOP_10/A3"] [tag "OWASP_AppSensor/IE1"] [tag "CAPEC-242"] [hostname "localhost"] [uri "/"] [unique_id "156198848361.198287"] [ref "v8,27t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls"]
....
....
---TqjMwy7h---I--
---TqjMwy7h---J--
---TqjMwy7h---Z--
If you do not see the following content, and the file is empty or it does not exist, then ModSecurity was not able to open this file for writing. Use the following fix -
# find out the user, Apache is running as
apache_user="$(ps -ef | egrep '(httpd|apache2|apache)' | grep -v `whoami` | grep -v root | head -n1 | awk '{print $1}')"
# find out the user, Apache is running as
apache_user="$(ps -ef | egrep '(httpd|apache2|apache)' | grep -v `whoami` | grep -v root | head -n1 | awk '{print $1}')"
Add this user to adm
adm
group which owns the Apache logs directory in Ubuntu.
sudo usermod -G adm www-data
sudo usermod -G adm www-data
Now, change the owner of Apache log directory to apache_user
apache_user
.
sudo chown -R $apache_user:$apache_user /var/log/apache2/*
sudo chown -R $apache_user:$apache_user /var/log/apache2/*
Now, ModSecurity should be able to append logs to the file modsec_audit.log
modsec_audit.log
.
Bonus: Enabling JSON logs
Note: Honestly speaking, I was not able to make it work every time. I do not
know what is the issue, but it works with some of the installations, and with
some of the installations, it just doesn't log anything to the audit
audit
directory. If anyone has managed to make it work consistently, please let me
know.
Edit (13/07/2020): The JSON logging works fine. The issue was that
ModSecurity did not have permission to create subdirectories in the Apache log
directory. I suppose it is something related to SELinux. However, a simple
solution is to add the user under which the Apache process runs to the adm
adm
group. It might not be the right solution security-wise. However, from a quick
remediation point of view, it works. Please let me know if you identify any
better solution to fix the problem.
Anyway, if you are like me, who do not like the default ModSecurity log format, ModSecurity provides an option to generate logs in JSON format as well. To enable JSON support, the YAJL library should be installed. We already installed this package when we were installing dependencies, so our ModSecurity setup is compiled with JSON support. Let us now configure ModSecurity to generate JSON logs.
Open the /etc/apache2/modsec/modsecurity.conf
/etc/apache2/modsec/modsecurity.conf
file and find the following
lines -
SecAuditLogType Serial
SecAuditLog /var/log/modsec_audit.log
SecAuditLogType Serial
SecAuditLog /var/log/modsec_audit.log
Once you have found the following lines, replace these lines with the following lines
SecAuditLogFormat JSON
SecAuditLogType Parallel
SecAuditLog /var/log/apache2/modsec_audit.log
SecAuditLogStorageDir /var/log/apache2/audit/
SecAuditLogFileMode 0644
SecAuditLogDirMode 0755
SecAuditLogFormat JSON
SecAuditLogType Parallel
SecAuditLog /var/log/apache2/modsec_audit.log
SecAuditLogStorageDir /var/log/apache2/audit/
SecAuditLogFileMode 0644
SecAuditLogDirMode 0755
Restart Apache server
sudo systemctl restart apache2
sudo systemctl restart apache2
Now, go to /var/log/apache2/
/var/log/apache2/
directory and create audit
audit
folder.
sudo usermod -G adm $apache_user
cd /var/log/apache2
sudo mkdir audit
# make `apache_user` owner of this directory as well...
sudo chown -R $apache_user:$apache_user /var/log/apache2/audit
sudo usermod -G adm $apache_user
cd /var/log/apache2
sudo mkdir audit
# make `apache_user` owner of this directory as well...
sudo chown -R $apache_user:$apache_user /var/log/apache2/audit
Now, ModSecurity should be able to generate JSON logs in this directory. ModSecurity generates logs in the following format -
ubuntu@server:/var/log/apache2$ tree audit
audit
└── 20190701
├── 20190701-1132
│ ├── 20190701-113225-156196094515.868593
│ └── 20190701-113226-156196094691.154769
├── 20190701-1211
│ ├── 20190701-121122-156196328239.048942
│ └── 20190701-121122-156196328243.018882
....
....
ubuntu@server:/var/log/apache2$ tree audit
audit
└── 20190701
├── 20190701-1132
│ ├── 20190701-113225-156196094515.868593
│ └── 20190701-113226-156196094691.154769
├── 20190701-1211
│ ├── 20190701-121122-156196328239.048942
│ └── 20190701-121122-156196328243.018882
....
....
Now, your site should be relatively more secure than before.
A warning, though
CRS is known to generate a lot of false-positive when enabled completely. We have not touched CRS paranoia levels. By default, it is set to paranoia level 1, which is known to produce false positives rarely, but still, as a measure of precaution, monitor your site's traffic for some time, and then decide if you need to disable some of the CRS rules or not.