Check the previous post on how to prepare and deploy an aspnet core application in ubuntu with nginx and MySQL. This is the second part of the guide.

The current setup is an aspnet core application already hosted and running in Ubuntu with nginx. this article will be focused on the security side of the application.

Block requests by IP

To stop nginx from serving requests on IP

sudo nano /etc/nginx/sites-available/default

Add the following section as the first server directive

server {
        listen  80;
        listen  443;
        server_name     "";
        return  444;
}

Check for syntax errors: sudo nginx -t and reload nginx service: sudo systemctl restart nginx

Firewall

ufw is the default firewall configuration tool for Ubuntu.

Allow http and ssh access (4743 is the port we updated ssh to listen for connections in the first part of this tutorial)


sudo ufw allow 4743
sudo ufw allow http

Run sudo ufw enable to enable ufw. and sudo ufw status verbose to check what is allowed.

Fail2ban

This application monitors server log files for failed authentication attempts and other suspicious user activity and can be configured to take different measures based on what a user needs.

This app should be already installed in your system, if not you can install it using apt package manager sudo apt-get install fail2ban.

Go to fail2ban directory cd /etc/fail2ban.

If you list the content of this folder, 2 are the files that we are interested on:

  • jail.conf
  • jail.local

The jail.conf file contains a basic configuration that you can use as a starting point, but it may be overwritten during updates. Fail2ban uses the separate jail.local file to actually read your configuration settings.

Open jail.local file:

sudo nano jail.local

Here is an explanation of the options found in the default section:

  • ignoreip: This option enables you to specify IP addresses or hostnames that fail2ban will ignore. For example, you could add your home or office IP address so fail2ban does not prevent you from accessing your own server. To specify multiple addresses, separate them with space. For example: ignoreip = 127.0.0.1/8 93.184.216.34
  • bantime: This option defines in seconds how long an IP address or host is banned. The default is 600 seconds (10 minutes).
  • maxretry: This option defines the number of failures a host is allowed before it is banned.
  • findtime: This option is used together with the maxretry option. If a host exceeds the maxretry setting within the time period specified by the findtime option, it is banned for the length of time specified by the bantime option.

Example:

[DEFAULT]
ignoreip = 127.0.0.1/8
bantime  = 1800
findtime  = 600
maxretry = 10

Explanation: If some ip except 127.0.0.1/8 tries 10 times (max retry) within 600 seconds (findtime), ban it for 1800 seconds (bantime)

Here I've compiled a default ban-model for my needs, you can remove or add other jails depending on your needs.

Here is the content of jail.local

[DEFAULT]
ignoreip = 127.0.0.1/8
bantime  = 1800
findtime  = 600
maxretry = 4

[ssh]

enabled  = true
port     = 4862
filter   = sshd
logpath  = /var/log/auth.log


#
## ban clients attempting to use our Nginx server as an open proxy.
#

[nginx-noproxy]

enabled  = true
port     = http,https
filter   = nginx-noproxy
logpath  = /var/log/nginx/access.log
maxretry = 2

#
## Block known bots
#

[nginx-badbots]

enabled  = true
port     = http,https
filter   = nginx-badbots
logpath  = /var/log/nginx/access.log
maxretry = 2
action   = cloudflare-firewall

#
## Block users searching for scripts
#

[nginx-noscript]

enabled  = true
port     = http,https
filter   = nginx-noscript
logpath  = /var/log/nginx/access.log
maxretry = 2
action   = cloudflare-firewall

#
## Block users which try to use home-based servings for nginx
#
[nginx-nohome]

enabled  = true
port     = http,https
filter   = nginx-nohome
logpath  = /var/log/nginx/access.log
maxretry = 2

#
## Block users accessing this server by IP
#
[nginx-444]

enabled = true
port    = http,https
filter  = nginx-444
logpath = /var/log/nginx/access.log
maxretry= 2

Save and exit, let's go to filter.d and create the necessary filters for our activated jails:

cd filter.d
sudo nano nginx-noproxy.conf

Use this content:

[Definition]
failregex = ^<HOST> -.*GET http.*
ignoreregex =

To block the known bots, just use the content of the Apache filter:

sudo cp apache-badbots.conf  nginx-badbots.conf

Block the script bots:

sudo nano nginx-noscript.conf

Add the following content:


[Definition]
failregex = ^<HOST> -.*GET.*(\.php|\.asp|\.exe|\.pl|\.cgi|\.scgi)
ignoreregex =

Block access to nginx as home-based

sudo nano nginx-nohome.conf

[Definition]
failregex = ^<HOST> -.*GET .*/~.*
ignoreregex =

Block access by IP in this server:

sudo nano nginx-444.conf


[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*HTTP.*" 444
ignoreregex =

Restart fail2ban service: sudo systemctl restart fail2ban and then check the jails running: sudo fail2ban-client status

Status of fail2ban service will throw a warning similar to "Unable to read action 'cloudflare-firewall'"

This is caused byno-scripts and badbots sections. No worry, we will fix this in the next step.

If you didn't specify your ip at the beginning and then somehow managed to lock yourself out by testing jails, use sudo fail2ban-client set <JAIL_NAME_HERE> unbanip <IP_TO_UNBAN> to unban an IP from a specific jail.

Fail2ban and Cloudflare

Proxying my site through Cloudflare network for me has been a huge help! I enjoy the fast cache response from their global network of data centers, but what I really love is the easy process to set up some very important security steps. Specifically the ability to block different offending IPs automatically based on their behavior.

Get the real client IP when forwarding requests from nginx to 3'rd party apps

Configuring nginx is pretty easy, we will simply activate set_real_ip_from IP.

What we will do is create a file-name.conf inside conf.d directory, and then nginx will pick it up automatically (remember the default include directive in nginx.conf file from the previous post?).

sudo nano /etc/nginx/conf.d/cloudflare-ips.conf

Insert the following content:


# From https://www.cloudflare.com/ips
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/12;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;

# IPv6
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;

real_ip_header CF-Connecting-IP;

As always, test the nginix configuration and then restart the service.


sudo nginx -t
sudo systemctl restart nginx

Setup a bash script to place HTTP requests with Cloudflare API

We could implement this in any language but none of them would be as cool as a bash script, so Credits for this step should go to Antoine Aflalo.

Install [JQ](sudo apt-get install jq)

sudo apt-get install jq

Create the script file

sudo nano /usr/local/sbin/cloudflare-firewall

And paste the following content


# !/bin/bash
# Antoine Aflalo (https://www.aaflalo.me)
# Create a IP ban on CloudFlare.
# Remove a IP ban on CloudFlare.
#
# usage: cloudflare-firewall <cfuser> <cftoken> <cfzoneid> <note>
# <cfuser>      :   You CloudFlare username
# <cftoken>     :   CF API Token in your profile
# <cfzoneid>    :   The ID assigned by CloudFlare to your website
# <note>        :   An optional note to the firewall rule

add() {
    local IP="${1}"; shift
    local NOTE="$@"

    curl -g -X POST "https://api.cloudflare.com/client/v4/zones/${CF_ZONEID}/firewall/access_rules/rules" \
     -H "X-Auth-Email: $CF_USER" \
     -H "X-Auth-Key: $CF_TOKEN" \
     -H "Content-Type: application/json" \
     --data @- << EOF
{"mode":"challenge","configuration":{"target":"ip","value":"$IP"},"notes":"$NOTE"}
EOF
}

remove() {
    local IP="${1}"

    local RULE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONEID}/firewall/access_rules/rules?configuration_target=ip&configuration_value=${IP}" \
     -H "X-Auth-Email: $CF_USER" \
     -H "X-Auth-Key:  $CF_TOKEN" \
     -H "Content-Type: application/json" | jq ".result|.[]|.id")
     RULE_ID="${RULE_ID%\"}"
     RULE_ID="${RULE_ID#\"}"

     curl -X DELETE "https://api.cloudflare.com/client/v4/zones/${CF_ZONEID}/firewall/access_rules/rules/${RULE_ID}" \
     -H "X-Auth-Email: $CF_USER" \
     -H "X-Auth-Key:  $CF_TOKEN" \
     -H "Content-Type: application/json" \
     --data '{"cascade":"basic"}'
}

CF_USER="$1"; shift
CF_TOKEN="$1"; shift
CF_ZONEID="$1"; shift
HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(add|remove)$ ]]; then
  "$HANDLER" "$@"
fi

Mark this script as executable:


sudo chmod +x /usr/local/sbin/cloudflare-firewall

Setup a fail2ban custom action

In this step, we create a custom action that will be taken when a fail2ban jail has been triggered. for this, you will need to prepare your CloudFlare Username/email, API key, and Zone Id.

Login to your Cloudflare account, select the website and open Overview tab.

Click get an API Key link and follow the steps for a global API key, Copy the generated token, and the Zone Id token.

Create an action file:


sudo nano /etc/fail2ban/action.d/cloudflare-firewall.conf

Paste the following content:


[Definition]

# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# Values:  CMD
#
actionstart =

# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
#
actionstop =

# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# Values:  CMD
#
actioncheck =

# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
actionban = /usr/local/sbin/cloudflare-firewall <cfuser> <cftoken> <cfzone> add <ip> "<name> after <failures> failures at <time>"
# Option:  actionunban
# Notes.:  command executed when unbanning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
actionunban = /usr/local/sbin/cloudflare-firewall <cfuser> <cftoken> <cfzone> remove <ip>

[Init]

# YOUR_CLOUDFLARE_API_TOKEN_HERE
cftoken =
# YOUR_EMAIL_FOR_CLOUDFLARE_HERE
cfuser =
# YOUR_ZONE_ID_HERE
cfzone =
# Name of ban
name = fail2ban

Restart your fail2ban service and check its status. If every step is finished as expected it should be working properly now.

Though this guide may be used as a starting point for beginners, it is not intended for every case.

"You either have been hacked, or you don't know it yet"

Security is a never-ending process! It usually depends on how much comfort and performance you are willing to sacrifice for it. Consider following the best practices, update your tools and hiring a security expert if needed.

I hope you enjoyed reading this post!

If you think I've missed some important point, please let me know in the comments.