-
Notifications
You must be signed in to change notification settings - Fork 53
/
auto-blockip.journald
executable file
·157 lines (116 loc) · 6.32 KB
/
auto-blockip.journald
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/bin/bash
# by Todd Stein
# Monday, May 05 2014
# Automatically blocks nasty IP addresses
# adapted for journald and firewalld on Dec 12, 2017
# PATH definition is required for successful cron invocation
PATH=$HOME/bin:/usr/local/install/xcat/bin:/usr/local/bin:/usr/bin:/sbin:/bin:$PATH
MAX_ALLOWED_FAILURES=5 # more than this many failed logins = ban
LOOK_BACK=5 # number of minutes to search back through /var/log/secure for auth failures
MOLE_LIMIT=5 # on the Nth ip from the same /24, the whole subnet will be blocked instead; whack-a-mole bad.
EMAIL_ADDRESS=tbs@usc.edu # email will be sent here
COUNTRY_FILE=/tmp/$(basename $0)_country_codes # used for country name lookups
LOG_FILE=/tmp/$(basename $0)_block_log # log ip blocks here -- existing entries won't be blocked again
#WHITELIST="68.181|128.125|10.125|10.126|192.168" # pipe-delimited list of whitelisted ip address prefixes
# initialize $LOG_FILE if necessary
[ -f "$LOG_FILE" ] || >"$LOG_FILE"
# download country database if necessary
[ -f "$COUNTRY_FILE" ] || curl -s http://www.geonames.org/countries/ >"$COUNTRY_FILE"
blockIP() { # input is a string
local ip=$1
# this will be used to count how many attacks from the same /24 have occurred
local escaped_ip="${ip//./\.}"
local same_subnet_match_string="^rule family=\"ipv4\" source address=\"${escaped_ip%.*}\.[0-9]{1,3}\" reject$"
# if we've seen fewer than $MOLE_LIMIT attackers from this /24
if [[ $(firewall-cmd --list-rich-rules | egrep -c "$same_subnet_match_string") -lt $MOLE_LIMIT ]]; then
# we only want to block the IP
local ip_search_string="^rule family=\\\"ipv4\\\" source address=\\\"($escaped_ip|${escaped_ip%.*}.0/24)\\\" reject$"
local command="
if ! firewall-cmd --list-rich-rules | egrep -q \"$ip_search_string\"; then
firewall-cmd -q --add-rich-rule=\"rule family='ipv4' source address='$ip' reject\"
fi
"
else
# we want to block the whole subnet
subnet="${ip%.*}.0/24"
local subnet_search_string="^rule family=\"ipv4\" source address=\"${subnet//./\.}\" reject$"
local command="
if ! firewall-cmd --list-rich-rules | egrep -q \"$subnet_search_string\"; then
firewall-cmd -q --add-rich-rule=\"rule family='ipv4' source address='$subnet' reject\"
fi
"
fi
# block it!
eval "$command"
# if we're on an xcat-managed cluster, block $ip on all heanodes, too
which psh &>/dev/null && psh headnodes "$command"
# log that we blocked
printf "%s\t%s\n" "$(date '+%b %d %H:%M:%S')" "${subnet:-$ip}" >>"$LOG_FILE"
}
getIPandFQDN() { # input is a string
local host=$1
# if $host is already an IP address
if [[ $host =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
ip=$host
fqdn=$(host $host | awk '/domain name pointer/ {print $NF; exit}')
else
fqdn=$host
ip=$(host $host | grep 'has address' | egrep -om1 '([0-9]{1,3}\.){3}[0-9]{1,3}$')
fi
}
getWhoisAndCountry() { # input is an ip address
local host=$1
sleep 1 # be respectful of the whois server
whois=$(whois $host)
# try to determine country code
local country_code=$(printf "%s\n" "$whois" | egrep -im1 'country(-code)?:')
if [[ -z $country_code ]]; then # if whois didn't return real info for $host, try querying the network handle instead
local handle=$(printf "%s\n" "$whois" | grep -v '^#' | egrep -om1 '\(.+\)' | egrep -o '[^()]+')
sleep 1 # be respectful of the whois server
local whois_2nd=$(whois $handle)
country_code=$(printf "%s\n" "$whois_2nd" | egrep -im1 'country(-code)?:')
[[ -n $country_code ]] && whois="$whois_2nd"
fi
# remove any formatting that could cause the email contents to be sent as an attachment instead of inline
# it seems to be caused by a carriage return or vertical tab or something else in [:space:] but not [:blank:]
whois="$(printf "%s\n" "$whois" | sed -r 's/[^[:blank:][:graph:]]//g')"
# extract the actuall country code from the line. the final egrep is required sometimes, presumably because of hidden characters.
country_code=$(printf "%s\n" "$country_code" | awk -F'[ :]+' '{print toupper($NF)}' | egrep -o "[A-Z]+")
# translate the code to an actual name.
country=$(grep "name=\"$country_code\"" "$COUNTRY_FILE" | egrep -o 'href="[^"]+">([^<]+)' | sed -r 's/.+>//')
}
sendMail() { # no input
# if we blocked a subnet
if [[ $subnet ]]; then
subject="Squashed breakin attempts from $subnet${country:+ ($country)}"
heading="An attack was detected from $ip${fqdn:+ ($fqdn)}, and all packets from $subnet are now being dropped by $HOSTNAME as a result. There have been at least $MOLE_LIMIT breach attempts from this /24, so a larger mallet has been employed. The following lines provide information about the remote host, as well as a recent history of communications from it. The last ${LOOK_BACK} minutes of the log were used to determine that the activity was malevolent."
else
subject="Squashed breakin attempt from $ip${country:+ ($country)}"
heading="Packets from $ip${fqdn:+ ($fqdn)} are now being dropped by $HOSTNAME. The following lines provide information about the remote host, as well as a recent history of communications from it. The last ${LOOK_BACK} minutes of the log were used to determine that the activity was malevolent."
fi
body="$(printf "%s\n\n\n%s\n\n\n%s" "$heading" "$whois" "$log")"
mail -s "$subject" "$EMAIL_ADDRESS" <<<"$body"
}
# generate list of possible attackers
attackers=$(journalctl --since "$LOOK_BACK minutes ago" | egrep 'sshd\[[0-9]+\]:.*(Failed password|Invalid user)' | egrep -o '\bfrom ([0-9]{1,3}\.){3}[0-9]{1,3}\b' | sort | uniq -c | sort -nr | awk -v m=$MAX_ALLOWED_FAILURES -F '[= ]+' '{if ($2>m) {print $NF}}')
for rhost in $attackers; do
unset ip fqdn whois country
# set $ip and $fqdn
getIPandFQDN $rhost
# ensure $ip is not in USC address space and/or $fqdn doesn't end in .usc.edu. Ensure it's not a cluster machine.
egrep -q "\b(${rhost//./\.}|${ip//./\.})\b" /etc/hosts || \
[[ $fqdn =~ \.usc\.edu$ ]] || \
[[ $WHITELIST && $ip =~ ^(${WHITELIST//./\.}) ]]
[[ $? == 0 ]] && continue
# ensure the attacker is not already blocked, preventing multiple blocks
escaped_ip="${ip//./\.}"
firewall-cmd --list-rich-rules | grep -Eq "^rule family=\"ipv4\" source address=\"($escaped_ip|${escaped_ip%.*}.0/24)\" reject$"
if [[ $? != 0 ]]; then
blockIP $ip
# grab full secure history of $rhost
log=$(journalctl --since "24 hours ago" | grep -E "\b${rhost//./\.}\b")
# set $whois and $country
getWhoisAndCountry $ip
sendMail
fi
done