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…
	
	Add table
		Add a link
		
	
		Reference in a new issue