Add comments.
This commit is contained in:
parent
a18272ac23
commit
498c367f26
1 changed files with 120 additions and 22 deletions
|
@ -1,17 +1,43 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
MY_IP="94.199.214.20"
|
################################################################################
|
||||||
|
# #
|
||||||
|
# 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.
|
||||||
|
MY_IP="94.199.214.20"
|
||||||
|
# Set the desired port to monitor.
|
||||||
|
MY_PORT="443"
|
||||||
|
|
||||||
|
# After this point, no editing is required.
|
||||||
|
# Define the files that will contain the addresses an subnets.
|
||||||
|
fileraw="raw-http.txt"
|
||||||
file8="sorted-http-8.txt"
|
file8="sorted-http-8.txt"
|
||||||
file16="sorted-http-16.txt"
|
file16="sorted-http-16.txt"
|
||||||
file24="sorted-http-24.txt"
|
file24="sorted-http-24.txt"
|
||||||
file32="sorted-http-32.txt"
|
file32="sorted-http-32.txt"
|
||||||
|
# This file will contain the addresses to be banned.
|
||||||
banlist="banlist.txt"
|
banlist="banlist.txt"
|
||||||
|
|
||||||
|
# These suffixes must be appended to the respective addresses and subnets.
|
||||||
ext8=".0.0.0/8"
|
ext8=".0.0.0/8"
|
||||||
ext16=".0.0/16"
|
ext16=".0.0/16"
|
||||||
ext24=".0/24"
|
ext24=".0/24"
|
||||||
ext32="/32"
|
ext32="/32"
|
||||||
|
|
||||||
|
# Define some constants to format the output in a colorful way.
|
||||||
red="\033[38;2;255;0;43m"
|
red="\033[38;2;255;0;43m"
|
||||||
yellow="\033[38;2;255;204;0m"
|
yellow="\033[38;2;255;204;0m"
|
||||||
green="\033[38;2;0;179;89m"
|
green="\033[38;2;0;179;89m"
|
||||||
|
@ -19,24 +45,33 @@ blue="\033[38;2;0;85;255m"
|
||||||
bold="\033[1m"
|
bold="\033[1m"
|
||||||
reset="\033[0m"
|
reset="\033[0m"
|
||||||
|
|
||||||
|
# Clean up when the script exits.
|
||||||
trap 'sudo -k; popd; rm -r ${tmpdir}' EXIT
|
trap 'sudo -k; popd; rm -r ${tmpdir}' EXIT
|
||||||
|
|
||||||
|
# Create a temp directory, chdir into it and create the (initially empty)
|
||||||
|
# banlist file.
|
||||||
tmpdir=$(mktemp -d)
|
tmpdir=$(mktemp -d)
|
||||||
pushd "${tmpdir}"
|
pushd "${tmpdir}"
|
||||||
touch "${banlist}"
|
touch "${banlist}"
|
||||||
|
|
||||||
netstat -nt | grep "${MY_IP}:443" | tr -s '[:blank:]' | cut -d' ' -f5 | cut -d: -f1 | sort > raw-http.txt
|
# 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}"
|
||||||
|
|
||||||
uniq -c raw-http.txt | sort -rn > "${file32}"
|
# Group and sort the data into the subnet-specific files.
|
||||||
cut -d. -f1-3 raw-http.txt | sort | uniq -c | sort -rn > "${file24}"
|
uniq -c "${fileraw}" | sort -rn > "${file32}"
|
||||||
cut -d. -f1-2 raw-http.txt | sort | uniq -c | sort -rn > "${file16}"
|
cut -d. -f1-3 "${fileraw}" | sort | uniq -c | sort -rn > "${file24}"
|
||||||
cut -d. -f1 raw-http.txt | sort | uniq -c | sort -rn > "${file8}"
|
cut -d. -f1-2 "${fileraw}" | sort | uniq -c | sort -rn > "${file16}"
|
||||||
|
cut -d. -f1 "${fileraw}" | sort | uniq -c | sort -rn > "${file8}"
|
||||||
|
|
||||||
|
# Determine the number of entries per file.
|
||||||
nlines32=$(cat "${file32}" | wc -l)
|
nlines32=$(cat "${file32}" | wc -l)
|
||||||
nlines24=$(cat "${file24}" | wc -l)
|
nlines24=$(cat "${file24}" | wc -l)
|
||||||
nlines16=$(cat "${file16}" | wc -l)
|
nlines16=$(cat "${file16}" | wc -l)
|
||||||
nlines8=$(cat "${file8}" | wc -l)
|
nlines8=$(cat "${file8}" | wc -l)
|
||||||
|
|
||||||
|
# Now let the user choose which file to process.
|
||||||
echo "We've got:"
|
echo "We've got:"
|
||||||
echo "[1] 32bit: ${nlines32} entries"
|
echo "[1] 32bit: ${nlines32} entries"
|
||||||
echo "[2] 24bit: ${nlines24} entries"
|
echo "[2] 24bit: ${nlines24} entries"
|
||||||
|
@ -44,22 +79,53 @@ echo "[3] 16bit: ${nlines16} entries"
|
||||||
echo "[4] 8bit: ${nlines8} entries"
|
echo "[4] 8bit: ${nlines8} entries"
|
||||||
read -p 'Which one do you want to work with (q=Quit) [1-4]? ' choice
|
read -p 'Which one do you want to work with (q=Quit) [1-4]? ' choice
|
||||||
|
|
||||||
|
# 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.
|
||||||
case "${choice}" in
|
case "${choice}" in
|
||||||
"1" ) file="${file32}" ; ext="${ext32}" ; nlines="${nlines32}" ;;
|
"1" )
|
||||||
"2" ) file="${file24}" ; ext="${ext24}" ; nlines="${nlines24}" ;;
|
file="${file32}"
|
||||||
"3" ) file="${file16}" ; ext="${ext16}" ; nlines="${nlines16}" ;;
|
ext="${ext32}"
|
||||||
"4" ) file="${file8}" ; ext="${ext8}" ; nlines="${nlines8}" ;;
|
nlines="${nlines32}"
|
||||||
"Q" | "q" ) echo 'Kthxbye.'; exit ;;
|
;;
|
||||||
* ) echo "Invalid input: ${infile}. I'm out of here."; exit 1 ;;
|
"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
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo "Processing ${file}."
|
echo "Processing ${file}."
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# 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.
|
||||||
|
################################################################################
|
||||||
function setHilite() {
|
function setHilite() {
|
||||||
local count=$1
|
local count=$1
|
||||||
case "${choice}" in
|
case "${choice}" in
|
||||||
"1" )
|
"1" )
|
||||||
# /32
|
# /32: 0 <= green < 3 <= yellow < 5 <= red
|
||||||
if [ $count -ge 5 ] ; then
|
if [ $count -ge 5 ] ; then
|
||||||
hilite="${red}"
|
hilite="${red}"
|
||||||
elif [ $count -ge 3 ] ; then
|
elif [ $count -ge 3 ] ; then
|
||||||
|
@ -69,7 +135,7 @@ function setHilite() {
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"2" )
|
"2" )
|
||||||
# /24
|
# /24: 0 <= green < 7 <= yellow < 13 <= red
|
||||||
if [ $count -ge 13 ] ; then
|
if [ $count -ge 13 ] ; then
|
||||||
hilite="${red}"
|
hilite="${red}"
|
||||||
elif [ $count -ge 7 ] ; then
|
elif [ $count -ge 7 ] ; then
|
||||||
|
@ -79,7 +145,7 @@ function setHilite() {
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"3" )
|
"3" )
|
||||||
# /16
|
# /16: 0 <= green < 13 <= yellow < 25 <= red
|
||||||
if [ $count -ge 25 ] ; then
|
if [ $count -ge 25 ] ; then
|
||||||
hilite="${red}"
|
hilite="${red}"
|
||||||
elif [ $count -ge 13 ] ; then
|
elif [ $count -ge 13 ] ; then
|
||||||
|
@ -89,7 +155,7 @@ function setHilite() {
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"4" )
|
"4" )
|
||||||
# /8
|
# /8: 0 <= green < 21 <= yellow < 49 <= red
|
||||||
if [ $count -ge 49 ] ; then
|
if [ $count -ge 49 ] ; then
|
||||||
hilite="${red}"
|
hilite="${red}"
|
||||||
elif [ $count -ge 21 ] ; then
|
elif [ $count -ge 21 ] ; then
|
||||||
|
@ -99,12 +165,19 @@ function setHilite() {
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
* )
|
* )
|
||||||
# ???
|
# ???: We should never get here. As a fall-back, just use no
|
||||||
|
# highlighting.
|
||||||
hilite=""
|
hilite=""
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# 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.
|
||||||
|
################################################################################
|
||||||
function processFile () {
|
function processFile () {
|
||||||
local file="${1}"
|
local file="${1}"
|
||||||
local line=''
|
local line=''
|
||||||
|
@ -112,29 +185,54 @@ function processFile () {
|
||||||
local addr=''
|
local addr=''
|
||||||
local banaction=''
|
local banaction=''
|
||||||
local nline=1
|
local nline=1
|
||||||
|
# Read the contents from filedescriptor 3 (important: Don's use the
|
||||||
|
# standard filedescriptor because we need to handle user input from
|
||||||
|
# within the loop).
|
||||||
while IFS= read -r -u3 line ; do
|
while IFS= read -r -u3 line ; do
|
||||||
line="$(echo "${line}" | tr -s '[:blank:]')"
|
line="$(echo "${line}" | tr -s '[:blank:]')"
|
||||||
count="$(echo "${line}" | cut -d' ' -f2)"
|
count="$(echo "${line}" | cut -d' ' -f2)"
|
||||||
addr="$(echo "${line}" | cut -d' ' -f3-)${ext}"
|
addr="$(echo "${line}" | cut -d' ' -f3-)${ext}"
|
||||||
setHilite "${count}"
|
setHilite "${count}"
|
||||||
whois "${addr}"
|
whois "${addr}"
|
||||||
echo -en "Address ${bold}$((nline++)) of ${nlines}${reset}: Found '${blue}${addr}${reset}' ${hilite}${count}${reset} times. Ban [y/N/q]? "
|
echo -en "Address ${bold}$((nline++)) of ${nlines}${reset}: \
|
||||||
|
Found '${blue}${addr}${reset}' ${hilite}${count}${reset} times. Ban [y/N/s=No, \
|
||||||
|
and skip remaining]? "
|
||||||
read banaction
|
read banaction
|
||||||
case "${banaction}" in
|
case "${banaction}" in
|
||||||
"q" | "Q" ) echo "Aborting." ; return ;;
|
"s" | "S" )
|
||||||
"y" | "Y" ) echo -e "Adding '${blue}${addr}${reset}' to banlist."; echo "${addr}" >> "${banlist}" ;;
|
echo -e "Not banning '${blue}${addr}${reset}', \
|
||||||
"n" | "N" | * ) 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}'."
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
# Here goes: Pipe the file contents via filedescriptor 3.
|
||||||
done 3< "${file}"
|
done 3< "${file}"
|
||||||
echo "Processed all entries."
|
echo "Processed all entries in ${file}."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Invoke the processing function on the chosen file.
|
||||||
processFile "${file}"
|
processFile "${file}"
|
||||||
|
|
||||||
echo "These are the addresses to be banned:"
|
echo "These are the addresses to be banned:"
|
||||||
cat "${banlist}"
|
cat "${banlist}"
|
||||||
|
|
||||||
|
# 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.
|
||||||
while read -r addr ; do
|
while read -r addr ; do
|
||||||
echo "Banning ${addr} ..."
|
echo "Banning ${addr} ..."
|
||||||
sudo fail2ban-client set apache-badbots banip "${addr}"
|
sudo fail2ban-client set apache-badbots banip "${addr}"
|
||||||
done < "${banlist}"
|
done < "${banlist}"
|
||||||
|
|
||||||
|
echo -e "${green}All done!${reset}"
|
||||||
|
|
Loading…
Reference in a new issue