Published on Sunday, February 23rd 2020

Every site has a lot of weekly / daily attempts to abuse your forms to submit their bad content, create users, login... Here we will try to ban some of those IPs directly in the server firewall, so they can't even reach Drupal.

At first, this method might not be applicable for you. If you have a professional hosting / PaaS, your provider could (should ?) already take care of this. At least globally, maybe not specifically for Drupal attacks. For this method, you must have be able to log on your server with a user that has admin rights. This post is especially written for Debian Buster, but it should be more or less the same for other Debian versions. For other distributions, you might have to tweak some things.

Then, as you will see in the conclusion, I tend to think that this method is not sustainable in the long term. This really has to be taken as an experiment.

I also want to mention that this blog post is heavily inspired by Integrate Honeypot with Fail2ban. Basically it was my starting point and I'm mostly complementing it here.

Step 1: Drupal configuration

Let's start on the Drupal side of things. You will need to enable the Syslog module (Core). Like the name suggests, this module will write the Drupal system logs into the linux syslog. In Debian, this log is /var/log/syslog. Now, among other logs, each time a login attempt fails, it will be recorded in the syslog.

Then, if you want you can also protect the login form with Honeypot as well. You can also protect other forms such as the user registration form and the global contact form. The important part is to be sure to have the option Log blocked form submissions checked on Honeypot configuration.

Step 2: Fail2Ban configuration on your server

Fail2Ban is available in the Debian repositories:

sudo apt install fail2ban

It's advised to never edit the Debian configuration files, so we will create only .local files. Let's start with the global jails configuration:

sudo nano /etc/fail2ban/jail.d/global.local
[DEFAULT]
ignoreip = 127.0.0.1 1.2.3.4
findtime = 3600
bantime = 31536000
maxretry = 2

Replace 1.2.3.4 by your own IP(s) before anything else! Otherwise you could loose access to your remote server.

Then you can set global variables that will override the too low default values.

  • findtime (in seconds) will parse the logs for the last specified seconds. Do not enter a huge value here because this could impact the server performance.
  • bantime (in seconds) is the time for which the IPs will be banned. Don't hesitate here to have a big number. Here it's 1 year.
  • maxretry is the number of times an attempt can be retried.

Now let's define a configuration for Drupal jails:

sudo nano /etc/fail2ban/jail.d/drupal.local
#[drupal-auth]
#enabled = true
#port = http,https
#filter = drupal-auth
#logpath = /var/log/syslog

[drupal-auth-custom]
enabled = true
port = http,https
filter = drupal-auth-custom
logpath = /var/log/syslog

[drupal-honeypot-auth]
enabled = true
port = http,https
filter = drupal-honeypot-auth
logpath = /var/log/syslog

[drupal-honeypot-contact]
enabled = true
port = http,https
filter = drupal-honeypot-contact
logpath = /var/log/syslog

The first jail and filter, drupal-auth, already exists by default in Debian when installing Fail2Ban. However, I found that the regex was not working for me everytime. I still enabled it in the beginning. I added a custom version that works better, drupal-auth-custom, and I deactivated the first one. Finally I added 2 jails and filters with Honeypot.

Finally, we have to create the filters that we declared in those jails. The first filter also exists after Fail2Ban installation, so let's create the 3 others:

sudo nano /etc/fail2ban/filter.d/drupal-auth-custom.local
# Fail2Ban configuration file for Drupal failed login attempts
# Author: Tipi Koivisto
# taken from fail2ban drupal module <a href="https://www.drupal.org/project/fail2ban">https://www.drupal.org/project/fail2ban</a>
[Definition]
# Option: failregex Notes.: regex to match the password failure messages in the
# logfile. The host must be matched by a group named "host". The tag "<HOST>"
# can be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) Values: TEXT
failregex = \|\d*\|user\|<HOST>\|.*Login attempt failed for \s?
# Option: ignoreregex Notes.: regex to ignore. If this regex matches, the line is
# ignored. Values: TEXT
#ignoreregex =
sudo nano /etc/fail2ban/filter.d/drupal-honeypot-auth.local
# Fail2Ban configuration file for Drupal Honeypot blocked form submissions
# copied from Author: Tipi Koivisto
[Definition]
# Option: failregex Notes.: regex to match the password failure messages in the
# logfile. The host must be matched by a group named "host". The tag "<HOST>"
# can be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) Values: TEXT
failregex = \|\d*\|honeypot\|<HOST>\|.*\|Soumission bloquée pour user_.*
# Option: ignoreregex Notes.: regex to ignore. If this regex matches, the line is
# ignored. Values: TEXT
#ignoreregex =
sudo nano /etc/fail2ban/filter.d/drupal-honeypot-contact.local
# Fail2Ban configuration file for Drupal Honeypot blocked form submissions
# copied from Author: Tipi Koivisto
[Definition]
# Option: failregex Notes.: regex to match the password failure messages in the
# logfile. The host must be matched by a group named "host". The tag "<HOST>"
# can be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) Values: TEXT
failregex = \|\d*\|honeypot\|<HOST>\|.*\|Blocked submission of contact_message_contact_form.*
# Option: ignoreregex Notes.: regex to ignore. If this regex matches, the line is
# ignored. Values: TEXT
#ignoreregex =

Notice that in /etc/fail2ban/filter.d/drupal-honeypot-auth.local, I had to use the french translation of the message because that's how Honeypot logs it for me. Also, you might have another form ID in /etc/fail2ban/filter.d/drupal-honeypot-contact.local

At last, restart the Fail2Ban service:

sudo systemctl restart fail2ban

Testing it

Careful if you try to pretend to be a hacker to see if your configuration works, you could be banned from your own server! I would just recommend to wait a little bit for real cases.

See if the jails are all active (you should have 5 with the default sshd jail):

sudo fail2ban-client status

See the status of a jail and if it has banned IPs:

sudo fail2ban-client status drupal-auth-custom

Test a jail filter:

sudo fail2ban-regex /var/log/syslog /etc/fail2ban/filter.d/drupal-auth-custom.local

One last word: when you test fail2ban-regex, if you find matches, those will not be banned. Same thing when you restart the server: already processed logs will not be taken into account. One would have to reset Fail2Ban's database or do other hacky things but it's not really worth it. After all, you've lived with those unbanned IPs for a while now, right ?

Results after 48 hours

After 48 hours I have:

  • 3 IPs banned by drupal-auth
  • 9 IPs banned by drupal-auth-custom which include the 3 above. That's why I deactivated drupal-auth and use only the custom version.
  • 0 IPs banned by drupal-honeypot-auth (I will have to check that it works).
  • 10 IPs banned by drupal-honeypot-contact

So a total of 19 IPs and they are all different.

Conclusion

For now I'm happy with this method but it should not be sustainable in the long term. With already 19 different IPs in 48 hours, if it goes on at the same rate for 1 year I will have around 3500 Iptables rules. This without counting the bans with the standard sshd jail. This should slow down too much my server.

Most importantly, I'm only banning on the basis of 3 Drupal specific rules, which miss the huge part of other attack attempts.

I'll keep the experiment going on for some time, just to see. In the end, I think that it might not be worth the effort. Configuring a more robust system would probably imply to use IPset which can handle large amounts of IPs. Also, it might be interesting to import or sync IP blacklists collected by a service and to contribute to this service data with your Fail2Ban. At this point, work is starting to add up and either you are really passionate about this, or you just take a very good hosting provider.

Update 2020-08-25: now 258 banned IPs for drupal-honeypot-contact, 199 for drupal-auth-custom and the rest is negligible. I've set up 1 year bans. Seems pretty much sustainable form my website, finally.

Commentaires

Cool, I have to do as well, but I do not understand how the HOST (the IP to ban) is recovered in your filters because you do not use the "<HOST>" tag?

Hi Steve, oh thanks! I do have the <HOST> tags, I just didn't pay attention and didn't use &lt; and &gt; in my post! It's corrected now :)

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.