ddos-mitigator/ddos-mitigator/superscript.sh

250 lines
7.7 KiB
Bash
Raw Normal View History

2020-06-23 02:16:06 +02:00
#!/bin/sh
2020-06-23 03:07:09 +02:00
################################################################################
# #
# Try and prevent apache overloads by banning IP addresses that have (too) #
# many open connections. #
# This script uses netstat to determine the connections to the HTTPS port of #
# the host machine and provides automated whois information retrieval based on #
# the address or the /24- /16- or /8-subnet thereof. Addresses (or subnets) #
# are presented to the user in order of descending connection count. For each #
# address (or subnet), the user can choose to ban or ignore it. Addresses (or #
# subnets) chosen to be banned will be blocked by the apache-badbots jail of #
# fail2ban. #
# Author: Manuel Friedli, <manuel@fritteli.ch> #
# This script is licenced under the GNU General Public Licence, version 3 or #
# later. #
# #
################################################################################
# Set the host's own IP address. So far, only an IPv4 address is supported.
2020-06-23 02:16:06 +02:00
MY_IP="94.199.214.20"
2020-06-23 03:07:09 +02:00
# Set the desired port to monitor.
MY_PORT="443"
2020-06-23 02:16:06 +02:00
2020-06-23 03:07:09 +02:00
# After this point, no editing is required.
# Define the files that will contain the addresses an subnets.
fileraw="raw-http.txt"
2020-06-23 02:16:06 +02:00
file8="sorted-http-8.txt"
file16="sorted-http-16.txt"
file24="sorted-http-24.txt"
file32="sorted-http-32.txt"
2020-06-23 03:07:09 +02:00
# This file will contain the addresses to be banned.
2020-06-23 02:16:06 +02:00
banlist="banlist.txt"
# This file contains the output of the last invocation of whois
whoisoutput="whois.txt"
2020-06-23 02:16:06 +02:00
2020-06-23 03:07:09 +02:00
# These suffixes must be appended to the respective addresses and subnets.
2020-06-23 02:16:06 +02:00
ext8=".0.0.0/8"
ext16=".0.0/16"
ext24=".0/24"
ext32="/32"
2020-06-23 03:07:09 +02:00
# Define some constants to format the output in a colorful way.
2020-06-23 02:16:06 +02:00
red="\033[38;2;255;0;43m"
yellow="\033[38;2;255;204;0m"
green="\033[38;2;0;179;89m"
blue="\033[38;2;0;85;255m"
bold="\033[1m"
reset="\033[0m"
2020-06-23 03:07:09 +02:00
# Clean up when the script exits.
2020-06-23 02:16:06 +02:00
trap 'sudo -k; popd; rm -r ${tmpdir}' EXIT
2020-06-23 03:07:09 +02:00
# Create a temp directory, chdir into it and create the (initially empty)
# banlist file.
2020-06-23 02:16:06 +02:00
tmpdir=$(mktemp -d)
pushd "${tmpdir}"
touch "${banlist}"
2020-06-23 03:07:09 +02:00
# Determine the current connections to the desired port; store the raw data in
# $fileraw.
netstat -nt | grep "${MY_IP}:${MY_PORT}" | tr -s '[:blank:]' | cut -d' ' -f5 \
| cut -d: -f1 | sort > "${fileraw}"
2020-06-23 02:16:06 +02:00
2020-06-23 03:07:09 +02:00
# Group and sort the data into the subnet-specific files.
uniq -c "${fileraw}" | sort -rn > "${file32}"
cut -d. -f1-3 "${fileraw}" | sort | uniq -c | sort -rn > "${file24}"
cut -d. -f1-2 "${fileraw}" | sort | uniq -c | sort -rn > "${file16}"
cut -d. -f1 "${fileraw}" | sort | uniq -c | sort -rn > "${file8}"
2020-06-23 02:16:06 +02:00
2020-06-23 03:07:09 +02:00
# Determine the number of entries per file.
2020-06-23 02:16:06 +02:00
nlines32=$(cat "${file32}" | wc -l)
nlines24=$(cat "${file24}" | wc -l)
nlines16=$(cat "${file16}" | wc -l)
nlines8=$(cat "${file8}" | wc -l)
2020-06-23 03:07:09 +02:00
# Now let the user choose which file to process.
2020-06-23 02:16:06 +02:00
echo "We've got:"
echo "[1] 32bit: ${nlines32} entries"
echo "[2] 24bit: ${nlines24} entries"
echo "[3] 16bit: ${nlines16} entries"
echo "[4] 8bit: ${nlines8} entries"
read -p 'Which one do you want to work with (q=Quit) [1-4]? ' choice
2020-06-23 03:07:09 +02:00
# Based on the user's choice, initialize the variables $file, $ext and
# $nlines, which will be used after this point. Also, $choice will be
# used to color the output based on subnet-type.
2020-06-23 02:16:06 +02:00
case "${choice}" in
2020-06-23 03:07:09 +02:00
"1" )
file="${file32}"
ext="${ext32}"
nlines="${nlines32}"
;;
"2" )
file="${file24}"
ext="${ext24}"
nlines="${nlines24}"
;;
"3" )
file="${file16}"
ext="${ext16}"
nlines="${nlines16}"
;;
"4" )
file="${file8}"
ext="${ext8}"
nlines="${nlines8}"
;;
"Q" | "q" )
echo "You chose to abort. That's fine! Have a nice day!"
exit
;;
* )
echo "Invalid input: ${choice}. I'm out of here."
exit 1
;;
2020-06-23 02:16:06 +02:00
esac
echo "Processing ${file}."
2020-06-23 03:07:09 +02:00
################################################################################
# Set the highlighting color for the address count. The color goes from green
# (low count) over yellow (intermediate count) to red (high count). Depending
# on the size of the subnet (/8, /16, /24 or /32), the transitions from one
# color to the next happen at different values.
################################################################################
2020-06-23 02:16:06 +02:00
function setHilite() {
local count=$1
case "${choice}" in
"1" )
2020-06-23 03:07:09 +02:00
# /32: 0 <= green < 3 <= yellow < 5 <= red
2020-06-23 02:16:06 +02:00
if [ $count -ge 5 ] ; then
hilite="${red}"
elif [ $count -ge 3 ] ; then
hilite="${yellow}"
else
hilite="${green}"
fi
;;
"2" )
2020-06-23 03:07:09 +02:00
# /24: 0 <= green < 7 <= yellow < 13 <= red
2020-06-23 02:16:06 +02:00
if [ $count -ge 13 ] ; then
hilite="${red}"
elif [ $count -ge 7 ] ; then
hilite="${yellow}"
else
hilite="${green}"
fi
;;
"3" )
2020-06-23 03:07:09 +02:00
# /16: 0 <= green < 13 <= yellow < 25 <= red
2020-06-23 02:16:06 +02:00
if [ $count -ge 25 ] ; then
hilite="${red}"
elif [ $count -ge 13 ] ; then
hilite="${yellow}"
else
hilite="${green}"
fi
;;
"4" )
2020-06-23 03:07:09 +02:00
# /8: 0 <= green < 21 <= yellow < 49 <= red
2020-06-23 02:16:06 +02:00
if [ $count -ge 49 ] ; then
hilite="${red}"
elif [ $count -ge 21 ] ; then
hilite="${yellow}"
else
hilite="${green}"
fi
;;
* )
2020-06-23 03:07:09 +02:00
# ???: We should never get here. As a fall-back, just use no
# highlighting.
2020-06-23 02:16:06 +02:00
hilite=""
;;
esac
}
2020-06-23 03:07:09 +02:00
################################################################################
# Process the file denoted by $1. For each line in the file, the count and the
# address are displayed and a whois request is made. The user can then choose to
# ban or ignore the address. Addresses chosen to be banned are appended to the
# $banlist.
################################################################################
2020-06-23 02:16:06 +02:00
function processFile () {
local file="${1}"
local line=''
local count=0
local addr=''
local banaction=''
local nline=1
local country_cn=1
local source_apnic=1
2020-06-23 03:07:09 +02:00
# Read the contents from filedescriptor 3 (important: Don's use the
# standard filedescriptor because we need to handle user input from
# within the loop).
2020-06-23 02:16:06 +02:00
while IFS= read -r -u3 line ; do
line="$(echo "${line}" | tr -s '[:blank:]')"
count="$(echo "${line}" | cut -d' ' -f2)"
addr="$(echo "${line}" | cut -d' ' -f3-)${ext}"
setHilite "${count}"
whois "${addr}" | tee "${whoisoutput}"
grep -iq "^country: *cn$" "${whoisoutput}"
country_cn=$?
grep -iq "^source: *apnic$" "${whoisoutput}"
source_apnic=$?
if [[ ${country_cn} -eq 0 && ${source_apnic} -eq 0 ]] ; then
echo -e "${red}Country = CN and source = APNIC!${reset}"
fi
2020-06-23 03:07:09 +02:00
echo -en "Address ${bold}$((nline++)) of ${nlines}${reset}: \
Found '${blue}${addr}${reset}' ${hilite}${count}${reset} times. Ban [y/N/s=No, \
and skip remaining]? "
2020-06-23 02:16:06 +02:00
read banaction
case "${banaction}" in
2020-06-23 03:07:09 +02:00
"s" | "S" )
echo -e "Not banning '${blue}${addr}${reset}', \
skipping remaining addresses."
return
;;
"y" | "Y" )
echo -e "Adding '${blue}${addr}${reset}' to \
banlist."
echo "${addr}" >> "${banlist}"
;;
"n" | "N" | * )
echo -e "Not banning '${blue}${addr}${reset}'."
;;
2020-06-23 02:16:06 +02:00
esac
2020-06-23 03:07:09 +02:00
# Here goes: Pipe the file contents via filedescriptor 3.
2020-06-23 02:16:06 +02:00
done 3< "${file}"
2020-06-23 03:07:09 +02:00
echo "Processed all entries in ${file}."
2020-06-23 02:16:06 +02:00
}
2020-06-23 03:07:09 +02:00
# Invoke the processing function on the chosen file.
2020-06-23 02:16:06 +02:00
processFile "${file}"
echo "These are the addresses to be banned:"
cat "${banlist}"
2020-06-23 03:07:09 +02:00
# Make sure the user has to (re)-identify him- or herself before actually
# banning anything.
sudo -k
# Iterate over all addresses in $banlist and invoke fail2ban-client on each
# one of them.
2020-06-23 02:16:06 +02:00
while read -r addr ; do
echo "Banning ${addr} ..."
sudo fail2ban-client set apache-badbots banip "${addr}"
done < "${banlist}"
2020-06-23 03:07:09 +02:00
echo -e "${green}All done!${reset}"