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