Build Nginx OpenResty with Modsecurity
This article was originally posted 14 December 2022
What is OpenResty
OpenResty is a fork of nginx that contains a built in lua interpreter, allowing the web server to serve dynamic content on it’s own, or modify requests/responses going to a proxy.
What is a WAF
A web application firewall (WAF) is a protective mechanism which inspects inbound requests to a web server. It can prevent malicious actors from sending specially crafted requests to a resource behind a web server.
Requirements (What We’re Using)
- Debian 11 (Buster) on VM or Baremetal
- OpenResty 1.21
- ModSecurity 3 w/Nginx Connector
- Basic Nginx Knowledge
- Basic Linux Command Line Knowledge
Setting Up OpenResty with ModSecurity (WAF)
With the introduction out of the way, we can get started setting up our very own web server protected by a web application firewall.
Get the Dependencies
Time to get the dependencies. This is a long list, so be patient it may take some time depending on your exact configuration.
apt-get install -y \
g++ \
flex \
bison \
curl \
doxygen \
libyajl-dev \
libgeoip-dev \
libtool \
libssl-dev \
dh-autoreconf \
libcurl4-gnutls-dev \
libxml2 \
libperl-dev \
libpcre++-dev \
libxml2-dev;
Grab Sources
Get the sources for ModSecurity, ModSecurity-nginx and OpenResty
git clone https://github.com/SpiderLabs/ModSecurity
git clone https://github.com/SpiderLabs/ModSecurity-nginx.git —depth 1
curl -O https://openresty.org/download/openresty-1.21.4.1.tar.gz
tar xvf openresty-1.21.4.1.tar.gz
Build ModSecurity
Set up build dependencies, checkout the submodule, configure, and build Modsecurity which will be installed into the appropriate directory.
cd ModSecurity/
./build.sh
git submodule init && git submodule update
./configure
make -j2 && make install
Build OpenResty and ModSecurity-nginx
Here, we’ll build OpenResty itself, as well as the ModSecurity module. We’re going to include the same modules that nginx is built with in Debian 11.
cd ../openresty-1.21.4.1/
./configure \
--with-compat \
--with-http_realip_module \
--with-http_auth_request_module \
--with-http_v2_module \
--with-http_dav_module \
--with-http_slice_module \
--with-threads \
--with-pcre-jit \
--with-ipv6 \
--with-http_addition_module \
--with-http_geoip_module=dynamic \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_mp4_module \
--with-http_perl_module=dynamic \
--with-http_random_index_module \
--with-http_secure_link_module \
--with-http_sub_module \
--with-mail=dynamic \
--with-mail_ssl_module \
--with-stream=dynamic \
--with-stream_geoip_module=dynamic \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--add-dynamic-module=../ModSecurity-nginx/
make -j2 && make install
Configure ModSec
Now let’s configure ModSecurity. First, create a directory where ModSecurity configs will live with mkdir /usr/local/openresty/nginx/modsec
. Then grab the unicode mapping and the default configuration for ModSecurity with:
cp ../ModSecurity/unicode.mapping /usr/local/openresty/nginx/modsec/
curl https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended > /usr/local/openresty/nginx/modsec/modsecurity.conf
Modsecurity is a firewall, and as such it requires rules to function. You can grab the modsecurity core rule-set (Which covers most standard attack vectors) from the git repo with the command:
git clone https://github.com/coreruleset/coreruleset /usr/local/modsecurity-crs
Rename the example exclusions file, as well as the example core rule-set setup file. These are used to insert exclusions before the ruleset, and to pull in the relevant data inside of the core ruleset.
mv /usr/local/modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example \
/usr/local/modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
mv /usr/local/modsecurity-crs/crs-setup.conf.example /usr/local/modsecurity-crs/crs-setup.conf
Since this is a fresh install, it’s best to enable ModSecurity right from the start. By default the security engine runs in detect only mode, this can be changed by editting /usr/local/openresty/nginx/modsec/modsecurity.conf
and changing SecRuleEngine from DetectOnly to On
We’ll also need to tell ModSecurity where to find the rule files, use the following command to tell ModSecurity where the core rule-set lives.
cat <<EOF > /usr/local/openresty/nginx/modsec/main.conf
Include /etc/nginx/modsec/modsecurity.conf
Include /usr/local/modsecurity-crs/crs-setup.conf
Include /usr/local/modsecurity-crs/rules/*.conf
EOF
Configuring OpenResty
We now need to configure OpenResty to load the ModSecurity module, not only that, but we need to tell the webserver to actually use the configs that we just implemented! This can be done by editting /usr/local/openresty/nginx/conf/nginx.conf
inside that file we’ll add the following
# Insert at the root of the config. Preferably, this should go right at the top.
load_module /usr/local/openresty/nginx/modules/ngx_http_modsecurity_module.so;
# 'modsecurity' can go inside of the http block, server block, or even location block to control where ModSecurity is enforced.
# an example inside of the server block would look like:
server {
...
modsecurity on;
modsecurity_rules_file /usr/local/openresty/nginx/modsec/main.conf;
...
}
Tying Things Up
With everything in place, we’ll be able to run our web server and test ModSecurity. Assuming the default configuration is in place, run /usr/local/openresty/bin/openresty -t
which will check your configuration for errors. Once you’ve finished fixing any errors in the configuration run /usr/local/openresty/bin/openresty
to start the web server process and navigate to your machines IP address, we should see the following:
Next, add ?shell=/bin/bash to the end of the url, for example https://127.0.0.127/?shell=/bin/bash
. If everything is set up as expected, we should see a 403 forbidden message like below.
Congratulations! We’ve just set up a web server protected by ModSecurity. Keep checking back for the next article in this series where we go over maintaining this setup.