Bash script to block brute force attacks with .htaccess

Your site is under attack. May be not right now, but several times a day. Password guessing, security vulnerabilities - that kind of stuff. In the error log it looks like this:


[Thu Jul 13 05:28:49 2017] [error] [client 196.64.59.18] ModSecurity: Access denied with code 401 (phase 2). Operator GT matched 0 at USER:bf_block. [file "/etc/apache2/mod_security/custom/wpbrute.conf"] [line "20"] [id "9999001"] [msg "ip address blocked for 5 minutes, more than 15 login attempts in 3 minutes."] [hostname "giraffesdoexist.com"] [uri "/wp-login.php"] [unique_id "WWdngc26uBEAAGIsOAEAAABG"]

The server, Apache in my case, handles these attacks routinely, but morons come back over and over again. Apart from performance implications there's a small chance of successful attack, so I made a simple script to block hackers via .htaccess.

This script runs by a cron job and has three parameters:

blockbruteforcers.sh ../logs/error_log ../domains/.htaccess 200

It searches error_log for ip-addresses that appear there more than 200 times and adds those to .htaccess with "deny from" prefix. Here's the script source code, don't forget to add "Execute" attribute when you create it on your server:


#!/bin/bash
if [ $# -ne 3 ]; then
echo "Incorrect number of arguments. Should be 3: apache error log path, .htaccess file path, number of entries."
exit
fi

filename=$2
num_entries=$3
error_log=$1

#If .htaccess file does not exist, create it and add 2 first lines
if [ ! -f "$filename" ]; then
echo order allow,deny >> "$filename"
echo allow from all >> "$filename"
fi

#Check if .htaccess file has new line at the end. If not - add it.
c=tail -c 1 "$filename"
if [ "$c" != "" ]; then
echo >> "$filename"
fi

msg=""
arr=($(grep -o 'client [0-9.]*' "$error_log"| sort -r | uniq -c | sort -nr | awk -v pNum=$num_entries '$1>pNum {print $3}'))
for i in "${arr[@]}"
do
if ! grep -q -P "$i$" "$filename"; then
if [ ! -z "$msg" ]; then
msg+="\n"
fi
msg+="deny from ${i}"
fi
done

if [ ! -z "$msg" ]; then
echo -e "$msg" >> "$filename"
echo "$msg"
else
echo "no matching records found"
fi

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now