Security tuning

bunkerized-nginx comes with a set of predefined security settings that you can (and you should) tune to meet your own use case.

Miscellaneous

Here is a list of miscellaneous environment variables related more or less to security :

  • MAX_CLIENT_SIZE=10m : maximum size of client body

  • ALLOWED_METHODS=GET|POST|HEAD : list of HTTP methods that clients are allowed to use

  • DISABLE_DEFAULT_SERVER=no : enable/disable the default server (i.e. : should your server respond to unknown Host header ?)

  • SERVER_TOKENS=off : enable/disable sending the version number of nginx

HTTPS

Settings

Here is a list of environment variables and the corresponding default value related to HTTPS :

  • LISTEN_HTTP=yes : you can set it to no if you want to disable HTTP access

  • REDIRECT_HTTP_TO_HTTPS=no : enable/disable HTTP to HTTPS redirection

  • HTTPS_PROTOCOLS=TLSv1.2 TLSv1.3 : list of TLS versions to use

  • HTTP2=yes : enable/disable HTTP2 when HTTPS is enabled

  • COOKIE_AUTO_SECURE_FLAG=yes : enable/disable adding Secure flag when HTTPS is enabled

  • STRICT_TRANSPORT_SECURITY=max-age=31536000 : force users to visit the website in HTTPS (more info here)

Let’s Encrypt

Using Let’s Encrypt with the AUTO_LETS_ENCRYPT=yes environment variable is the easiest way to add HTTPS supports to your web services if they are connected to internet and you have public DNS A record(s).

You can also set the EMAIL_LETS_ENCRYPT environment variable if you want to receive notifications from Let’s Encrypt like expiration alerts.

Custom certificate(s)

If you have security constraints (e.g., local network, custom PKI, …) you can use custom certificates of your choice and tell bunkerized-nginx to use them with the following environment variables :

  • USE_CUSTOM_HTTPS=yes

  • CUSTOM_HTTPS_CERT=/path/inside/container/to/cert.pem

  • CUSTOM_HTTPS_KEY=/path/inside/container/to/key.pem

Here is a an example on how to use custom certificates :

$ ls /etc/ssl/my-web-app
cert.pem key.pem

$ docker run -p 80:8080 \
             -p 443:8443 \
             -v /etc/ssl/my-web-app:/certs:ro \
             -e USE_CUSTOM_HTTPS=yes \
             -e CUSTOM_HTTPS_CERT=/certs/cert.pem \
             -e CUSTOM_HTTPS_KEY=/certs/key.pem \
             ...
             bunkerity/bunkerized-nginx

Please note that if you have one or more intermediate certificate(s) in your chain of trust, you will need to provide the bundle to CUSTOM_HTTPS_CERT (more info here).

You can reload the certificate(s) (i.e., in case of a renewal) by sending a reload order to bunkerized-nginx.

Docker reload :

docker kill --signal=SIGHUP my-container

Swarm and Kubernetes reload (repeat for each node) :

$ curl http://node-local-ip:80/api-uri/reload

Linux reload :

$ /usr/sbin/nginx -s reload

Self-signed certificate

This method is not recommended in production but can be used to quickly deploy HTTPS for testing purposes. Just use the GENERATE_SELF_SIGNED_SSL=yes environment variable and bunkerized-nginx will generate a self-signed certificate for you :

$ docker run -p 80:8080 \
             -p 443:8443 \
             -e GENERATE_SELF_SIGNED_SSL=yes \
             ...
             bunkerity/bunkerized-nginx

Headers

Some important HTTP headers related to client security are sent with a default value. Sometimes it can break a web application or can be tuned to provide even more security. The complete list is available here.

You can also remove headers (e.g., too verbose ones) by using the REMOVE_HEADERS environment variable which takes a list of header name separated with space (default value = Server X-Powered-By X-AspNet-Version X-AspNetMvc-Version).

If you want to keep your application headers and tell bunkerized-nginx to not override it, just set the corresponding environment variable to an empty value (e.g., CONTENT_SECURITY_POLICY=, PERMISSIONS_POLICY=, …).

ModSecurity

ModSecurity is integrated and enabled by default alongside the OWASP Core Rule Set within bunkerized-nginx. To change this behaviour you can use the USE_MODSECURITY=no or USE_MODSECURITY_CRS=no environment variables.

We strongly recommend to keep both ModSecurity and the OWASP Core Rule Set enabled. The only downsides are the false positives that may occur. But they can be fixed easily and the CRS team maintains a list of exclusions for common application (e.g., wordpress, nextcloud, drupal, cpanel, …).

Tuning the CRS with bunkerized-nginx is pretty simple : you can add configuration before and after the rules are loaded. You just need to mount your .conf files into the /modsec-crs-confs (before CRS is loaded) and /modsec-confs (after CRS is loaded) volumes. If you are using Linux integration the special folders are /opt/bunkerized-nginx/modsec-confs and /opt/bunkerized-nginx/modsec-crs-confs.

Here is a Docker example to illustrate it :

$ cat /data/exclusions-crs/wordpress.conf
SecAction \
 "id:900130,\
  phase:1,\
  nolog,\
  pass,\
  t:none,\
  setvar:tx.crs_exclusions_wordpress=1"

$ cat /data/tuning-crs/remove-false-positives.conf
SecRule REQUEST_FILENAME "/wp-admin/admin-ajax.php" "id:1,ctl:ruleRemoveByTag=attack-xss,ctl:ruleRemoveByTag=attack-rce"
SecRule REQUEST_FILENAME "/wp-admin/options.php" "id:2,ctl:ruleRemoveByTag=attack-xss"
SecRule REQUEST_FILENAME "^/wp-json/yoast" "id:3,ctl:ruleRemoveById=930120"

$ docker run -p 80:8080 \
             -p 443:8443 \
             -v /data/exclusions-crs:/modsec-crs-confs:ro \
             -v /data/tuning-crs:/modsec-confs:ro \
             ...
             bunkerity/bunkerized-nginx

Bad behaviors detection

When attackers search for and/or exploit vulnerabilities they might generate some suspicious HTTP status codes that a “regular” user won’t generate within a period of time. If we detect that kind of behavior we can ban the offending IP address and force the attacker to come with a new one.

That kind of security measure is implemented and enabled by default in bunkerized-nginx. Here is the list of the related environment variables and their default value :

  • USE_BAD_BEHAVIOR=yes : enable/disable “bad behavior” detection and automatic ban of IP

  • BAD_BEHAVIOR_STATUS_CODES=400 401 403 404 405 429 444 : the list of HTTP status codes considered as “suspicious”

  • BAD_BEHAVIOR_THRESHOLD=10 : the number of “suspicious” HTTP status codes required before we ban the corresponding IP address

  • BAD_BEHAVIOR_BAN_TIME=86400 : the duration time (in seconds) of the ban

  • BAD_BEHAVIOR_COUNT_TIME=60 : the duration time (in seconds) to wait before resetting the counter of “suspicious” HTTP status codes for a given IP

Antibot challenge

Attackers will certainly use automated tools to exploit/find some vulnerabilities on your web services. One countermeasure is to challenge the users to detect if they look like a bot. It might be effective against script kiddies or “lazy” attackers.

You can use the USE_ANTIBOT environment variable to add that kind of checks whenever a new client is connecting. The available challenges are : cookie, javascript, captcha and recaptcha. More info here.

External blacklists

Distributed

This feature is in beta and will be improved regularly.

You can benefit from a distributed blacklist shared among all of the bunkerized-nginx users.

Each time a bunkerized-nginx instance detect a bad request, the offending IP is sent to a remote API and will enrich a database. An extract of the top malicious IP is downloaded on a periodic basis and integrated into bunkerized-nginx as a blacklist.

This feature is controlled with the USE_REMOTE_API=yes environment variable.

To avoid poisoning, in addition to the various security checks made by the API we only mark IP as bad in the database if it has been seen by one of our honeypots under our control.

DNSBL

Automatic checks on external DNS BlackLists are enabled by default with the USE_DNSBL=yes environment variable. The list of DNSBL zones is also configurable, you just need to edit the DNSBL_LIST environment variable which contains the following value by default bl.blocklist.de problems.dnsbl.sorbs.net sbl.spamhaus.org xbl.spamhaus.org.

User-Agents

Sometimes script kiddies or lazy attackers don’t put a “legitimate” value inside the User-Agent HTTP header so we can block them. This is controlled with the BLOCK_USER_AGENT=yes environment variable. The blacklist is composed of two files from here and here.

If a legitimate User-Agent is blacklisted, you can use the WHITELIST_USER_AGENT while still keeping the BLOCK_USER_AGENT=yes (more info here).

TOR exit nodes

Blocking TOR exit nodes might not be a good decision depending on your use case. We decided to enable it by default with the BLOCK_TOR_EXIT_NODE=yes environment variable. If privacy is a concern for you and/or your clients, you can override the default value (i.e : BLOCK_TOR_EXIT_NODE=no).

Please note that you have a concrete example on how to use bunkerized-nginx with a .onion hidden service here.

Proxies

This list contains IP addresses and networks known to be open proxies (downloaded from here). Unless privacy is important for you and/or your clients, you should keep the default environment variable BLOCK_PROXIES=yes.

Abusers

This list contains IP addresses and networks known to be abusing (downloaded from here). You can control this feature with the BLOCK_ABUSERS environment variable (default : yes).

Referrers

This list contains bad referrers domains known for spamming (downloaded from here). If one value is found inside the Referer HTTP header, request will be blocked. You can control this feature with the BLOCK_REFERRER environment variable (default = yes).

Limiting

Requests

To limit bruteforce attacks or rate limit access to your API you can use the “request limit” feature so attackers will be limited to X request(s) within a period of time for the same resource. That kind of protection might be useful against other attacks too (e.g., blind SQL injection).

Here is the list of related environment variables and their default value :

  • USE_LIMIT_REQ=yes : enable/disable request limiting

  • LIMIT_REQ_URL= : the URL you want to protect, use / to apply the limit for all URL

  • LIMIT_REQ_RATE=1r/s : the rate to apply for the resource, valid period are : s (second), m (minute), h (hour) and d (day)

  • `LIMIT_REQ_BURST=5 : the number of request tu put in a queue before effectively rejecting requests

  • LIMIT_REQ_DELAY=1 : the number of seconds to wait before we proceed requests in queue

Please note that you can apply different rate to different URL by appending a number as suffix (more info here).

Connections

Opening too many connections from the same IP address might be considered as suspicious (unless it’s a shared IP and everyone is sending requests to your web service). It can be a dos/ddos attempt too. Bunkerized-nginx levarages the ngx_http_conn_module from nginx to prevent users opening too many connections.

Here is the list of related environment variables and their default value :

  • USE_LIMIT_CONN=yes : enable disable connection limiting

  • LIMIT_CONN_MAX=50 : maximum number of connections per IP

Country

If the location of your clients is known, you may want to add another security layer by whitelisting or blacklisting some countries. You can use the BLACKLIST_COUNTRY or WHITELIST_COUNTRY environment variables depending on your approach. They both take a list of 2 letters country code separated with space.

Authentication

You can quickly protect sensitive resources (e.g. : admin panels) by requiring HTTP authentication. Here is the list of related environment variables and their default value :

  • USE_AUTH_BASIC=no : enable/disable auth basic

  • AUTH_BASIC_LOCATION=sitewide : location of the sensitive resource (e.g. /admin) or sitewide to force authentication on the whole service

  • AUTH_BASIC_USER=changeme : the username required

  • AUTH_BASIC_PASSWORD=changeme : the password required

  • AUTH_BASIC_TEXT=Restricted area : the text that will be displayed to the user

Please note that bunkerized-nginx also supports Authelia for authentication (see the corresponding environment variables and a full example).

Whitelisting

Adding extra security can sometimes trigger false positives. Also, it might be not useful to do the security checks for specific clients because we decided to trust them. Bunkerized-nginx supports two types of whitelist : by IP address and by reverse DNS.

Here is the list of related environment variables and their default value :

  • USE_WHITELIST_IP=yes : enable/disable whitelisting by IP address

  • WHITELIST_IP_LIST=23.21.227.69 40.88.21.235 50.16.241.113 50.16.241.114 50.16.241.117 50.16.247.234 52.204.97.54 52.5.190.19 54.197.234.188 54.208.100.253 54.208.102.37 107.21.1.8 : list of IP addresses and/or network CIDR blocks to whitelist (default contains the IP addresses of the DuckDuckGo crawler)

  • USE_WHITELIST_REVERSE=yes : enable/disable whitelisting by reverse DNS

  • WHITELIST_REVERSE_LIST=.googlebot.com .google.com .search.msn.com .crawl.yahoot.net .crawl.baidu.jp .crawl.baidu.com .yandex.com .yandex.ru .yandex.net : the list of reverse DNS suffixes to trust (default contains the list of major search engines crawlers)

Blacklisting

Sometimes it isn’t necessary to spend some resources for a particular client because we know for sure that he is malicious. Bunkerized-nginx nginx supports two types of blacklisting : by IP address and by reverse DNS.

Here is the list of related environment variables and their default value :

  • USE_BLACKLIST_IP=yes : enable/disable blacklisting by IP address

  • BLACKLIST_IP_LIST= : list of IP addresses and/or network CIDR blocks to blacklist

  • USE_BLACKLIST_REVERSE=yes : enable/disable blacklisting by reverse DNS

  • BLACKLIST_REVERSE_LIST=.shodan.io : the list of reverse DNS suffixes to never trust

Plugins

Some security features can be added through the plugins system (e.g., ClamAV, CrowdSec, …). You will find more info in the plugins section.

Container hardening

You will find a ready to use docker-compose.yml file focused on container hardening here.

Drop capabilities

By default, bunkerized-nginx runs as non-root user inside the container and should not use any of the default capabilities allowed by Docker. You can safely remove all capabilities to harden the container :

docker run ... --drop-cap=all ... bunkerity/bunkerized-nginx

No new privileges

Bunkerized-nginx should never tries to gain additional privileges through setuid/setgid executables. You can safely add the no-new-privileges security configuration when creating the container :

docker run ... --security-opt no-new-privileges ... bunkerity/bunkerized-nginx

Read-only

Since the locations where bunkerized-nginx needs to write are known, we can run the container with a read-only root file system and only allow writes to specific locations by adding volumes and a tmpfs mount :

docker run ... --read-only --tmpfs /tmp -v cache-vol:/cache -v conf-vol:/etc/nginx -v /path/to/web/files:/www:ro -v /where/to/store/certificates:/etc/letsencrypt bunkerity/bunkerized-nginx

User namespace remap

Another hardening trick is user namespace remapping : it allows you to map the UID/GID of users inside a container to another UID/GID on the host. For example, you can map the user nginx with UID/GID 101 inside the container to a non-existent user with UID/GID 100101 on the host.

Let’s assume you have the /etc/subuid and /etc/subgid files like this :

user:100000:65536

It means that everything done inside the container will be remapped to UID/GID 100101 (100000 + 101) on the host.

Please note that you must set the rights on the volumes (e.g. : /etc/letsencrypt, /www, …) according to the remapped UID/GID :

$ chown root:100101 /path/to/letsencrypt
$ chmod 770 /path/to/letsencrypt
$ docker run ... -v /path/to/letsencrypt:/etc/letsencrypt ... bunkerity/bunkerized-nginx