« Previous 1 2 3 Next »
A script for strict packet filter updates
Against the Wall
comment Module
To cancel locks after a certain time, the iptables comment
module must also be in place, either integrated directly into the kernel or downloaded as a kernel module. Without this feature, the script works, but it releases locked areas quite quickly once the logfile no longer documents the access attempts. The duration of the lock is thus automatically based on the interval of any logrotate
scripts. In the comments, note the time of the last lock. The script then automatically removes obsolete records after a configurable wait.
Script Structure
The script [8] has three functions: (1) setting up the system to use the rule chains; (2) removing the rules and setup without leaving any leftovers; and (3) executing the filter functions to detect IP addresses, prefixes, and AS numbers. Before the individual functions, the script defines the helper functions that assist iptables in creating and deleting rules (Listing 1).
Listing 1
updateFirewall.sh
001 #!/bin/bash 002 003 # Configuration 004 BLOCK_IP_THRESHOLD=1 005 BLOCK_PREFIX_THRESHOLD=3 006 BLOCK_ASN_THRESHOLD=3 007 008 # Automatically remove old rules after X seconds 009 UNBLOCK_TIME=$((60 * 60 * 24 * 30)) # Unblock after 30 days 010 011 # IPs that should never be locked (can also be a prefix) 012 LAST_RESORT_IPS=' 013 127.0.0.1 014 ' 015 016 FILTER=' 017 cat /var/log/secure | awk '\''$5~/^ssh/ && $6~/^Failed/ && $7~/^keyboard-interactive/ && $9~/[^invalid]/ {print $11}'\'' 018 cat /var/log/secure | awk '\''$5~/^ssh/ && $6~/^Failed/ && $7~/^keyboard-interactive/ && $9~/[invalid]/ {print $13}'\'' 019 ' 020 021 # Path to the client script at the University of Bonn 022 BART_CLIENT='./request.py' 023 024 ########################################################## 025 ##### Nothing has to be changed after this line ##### 026 ########################################################## 027 028 IPTABLES=`which iptables` 029 030 # Test to see whether iptables supports comments; otherwise, no auto-unblock 031 UNBLOCK_AUTO=0 032 033 OUT=`cat /proc/net/ip_tables_matches | grep 'comment'` 034 if [ "$OUT" = "comment" ]; then 035 UNBLOCK_AUTO=1 036 fi 037 038 function chain_exists 039 { 040 TABLENAME="-t filter" 041 CHAINNAME=$1 042 if [ "$2" != "" ]; then 043 TABLENAME="-t $1" 044 CHAINNAME=$2 045 fi 046 if [ "$CHAINNAME" != "" ]; then 047 ${IPTABLES} ${TABLENAME} -nL ${CHAINNAME} 2> /dev/null 1> /dev/null 048 RET=$? 049 return ${RET} 050 fi 051 } 052 053 function delete_rule 054 { 055 # Find correct rule parameters 056 RULE=`echo ${@} | awk 'BEGIN{FS=" -j"}; {print $1}'` 057 TARGET=`echo ${@} | awk 'BEGIN{FS=" -j"}; {print "-j"$2}'` 058 059 ${IPTABLES} -S | grep --regexp="$RULE" | grep --regexp="$TARGET" | cut -d " " -f 2- | xargs -L1 ${IPTABLES} -D 2> /dev/null 060 } 061 062 function insert_rule 063 { 064 # echo "insert: $@" 065 delete_rule $@ 066 067 if [ "${UNBLOCK_AUTO}" -ne 1 ]; then 068 ${IPTABLES} -I $@ 069 else 070 TS=$(date +%s) 071 ${IPTABLES} -I $@ -m comment --comment "Created: $TS" 072 fi 073 } 074 075 function add_rule 076 { 077 # echo "add: $@" 078 delete_rule $@ 079 080 if [ "${UNBLOCK_AUTO}" -ne 1 ]; then 081 ${IPTABLES} -A $@ 082 else 083 TS=$(date +%s) 084 ${IPTABLES} -A $@ -m comment --comment "Created: $TS" 085 fi 086 } 087 088 if [ "$1" = "setup" ]; then 089 # Deactivate comments to keep these rules permanently 090 UNBLOCK_AUTO=0 091 092 echo "Initialize iptables" 093 # Create rule chains 094 chain_exists LAST_RESORT || ${IPTABLES} -N LAST_RESORT 095 ${IPTABLES} -F LAST_RESORT 096 add_rule LAST_RESORT -j RETURN 097 for IP in "${LAST_RESORT_IPS}"; do 098 insert_rule LAST_RESORT -s ${IP} -j ACCEPT 099 done 100 101 chain_exists BLOCK_IP || ${IPTABLES} -N BLOCK_IP 102 ${IPTABLES} -F BLOCK_IP 103 add_rule BLOCK_IP -j RETURN 104 105 chain_exists BLOCK_PREFIX || ${IPTABLES} -N BLOCK_PREFIX 106 ${IPTABLES} -F BLOCK_PREFIX 107 add_rule BLOCK_PREFIX -j RETURN 108 109 chain_exists BLOCK_ASN || ${IPTABLES} -N BLOCK_ASN 110 ${IPTABLES} -F BLOCK_ASN 111 add_rule BLOCK_ASN -j RETURN 112 113 114 # Create rules in the INPUT chain 115 insert_rule INPUT -j BLOCK_IP 116 insert_rule INPUT -j BLOCK_PREFIX 117 insert_rule INPUT -j BLOCK_ASN 118 insert_rule INPUT -j LAST_RESORT 119 echo "Done" 120 exit 121 elif [ "$1" = "teardown" ]; then 122 echo "Remove all rules and chains" 123 delete_rule INPUT -j BLOCK_IP 124 delete_rule INPUT -j BLOCK_PREFIX 125 delete_rule INPUT -j BLOCK_ASN 126 delete_rule INPUT -j LAST_RESORT 127 128 ${IPTABLES} -F LAST_RESORT 2> /dev/null 129 ${IPTABLES} -X LAST_RESORT 2> /dev/null 130 131 ${IPTABLES} -F BLOCK_IP 2> /dev/null 132 ${IPTABLES} -X BLOCK_IP 2> /dev/null 133 134 ${IPTABLES} -F BLOCK_PREFIX 2> /dev/null 135 ${IPTABLES} -X BLOCK_PREFIX 2> /dev/null 136 137 ${IPTABLES} -F BLOCK_ASN 2> /dev/null 138 ${IPTABLES} -X BLOCK_ASN 2> /dev/null 139 echo "Done" 140 exit 141 fi 142 143 # Test whether setup is already performed 144 chain_exists BLOCK_IP || echo "Please start '$0 setup'"; exit 145 146 IPS="" 147 OLD_IFS=${iFS} 148 IFS=" 149 " 150 for RULE in ${FILTER}; do 151 # Apply the filter rule 152 eval FOUND_IPS=\`${RULE}\` 153 IPS="${IPS} 154 ${FOUND_IPS}" 155 done 156 157 # Filter by IP address, incidence count, and use of the IP threshold 158 IPS=`echo "${IPS}" | grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -n | uniq -c | awk '$1 >= '${BLOCK_IP_THRESHOLD}' {print $2}'` 159 160 # echo "${IPS}" 161 for IP in ${IPS}; do 162 insert_rule BLOCK_IP -s ${IP} -j DROP 163 done 164 165 166 167 # Finding and blocking the prefix 168 QUERY_RESPONSE=`echo "begin 169 prefix 170 $IPS 171 end" | netcat whois.cymru.com 43` 172 173 PREFIXES=`echo "${QUERY_RESPONSE}" | awk 'BEGIN{FS="|"}; {print $1" "$3}' | grep -v '^$' | sort -n | uniq -c | awk '$1 >= '${BLOCK_PREFIX_THRESHOLD}' {print $2" "$3}'` 174 175 # echo "${PREFIXES}" 176 for P in $( echo "${PREFIXES}" | awk '{print $2}' ); do 177 DEL_IPS=`echo "${QUERY_RESPONSE}" | grep "${P}" | awk 'BEGIN{FS="|"}; {print $2}' | sort -n | uniq | awk '{print $1}'` 178 for IP in ${DEL_IPS}; do 179 # echo "Delete single check for IP: ${IP}" 180 delete_rule BLOCK_IP -s ${IP} -j DROP 181 done 182 insert_rule BLOCK_PREFIX -s ${P} -j DROP 183 done 184 185 186 187 # Filter all prefixes of an AS 188 ASNLIST=`echo "${PREFIXES}" | awk '{print $1}' | sort -n | uniq -c | awk '$1 >= '${BLOCK_ASN_THRESHOLD}' {print $2}'` 189 190 if [ -x "${BART_CLIENT}" ]; then 191 for AS in ${ASNLIST}; do 192 PREFIX_LIST=`${BART_CLIENT} announced_prefixes $AS` 193 for P in ${PREFIX_LIST}; do 194 delete_rule BLOCK_PREFIX -s ${P} -j DROP 195 insert_rule BLOCK_ASN -s ${P} -j DROP 196 done 197 done 198 fi 199 200 201 202 # Discard old entries 203 if [ "${UNBLOCK_AUTO}" -eq 1 ]; then 204 NOW=$(date +%s) 205 RULES=`${IPTABLES} -S | cut -d " " -f 2-` 206 for R in $RULES; do 207 TIMESTAMP=`echo "${R}" | grep -o '"Created: .*"' | tr -d '"' | awk '{print $2}'` 208 if [ "$TIMESTAMP" != "" ] && [ "$TIMESTAMP" -lt $(($NOW - $UNBLOCK_TIME)) ]; then 209 delete_rule $R 210 fi 211 done 212 fi 213 IFS=${OLD_IFS}
Rules are deleted by specifying the filter conditions, or by specifying the line number (call iptables with --line-numbers
). Using line numbers entails the risk that changes have been made displaying the current rules and deletion through other scripts and that the script thus changes the wrong lines. To prevent this, you use the filter condition itself to remove the entries. Lines 53 to 60 is the delete_rule
function. To account for the automatically added timestamp in the optional comments, the function first takes the rules apart and then puts them back together with a slightly modified parameter order.
Processing Chain-by-Chain
The setup
and teardown
functions can be executed by simply appending the terms as parameters of the script. During setup (lines 88 to 120), the script first creates the three chains if they do not exist. It then removes all the rules from the chains so there is always an empty initial status at setup time. The end of each chain requires a rule to return the package to the calling chain. Without this rule, iptables does not examine the package. The required steps are shown in lines 109 to 111, which use the BLOCK_ASN
chain as an example. The helper function chain_exists
checks upfront to see whether a chain already exists; add_rule
then adds the rule. At the end of the function, the script adds the branch to the new chains in the INPUT
chain (lines 115 to 118).
When removing rules with teardown
(lines 121 to 141), the functions work in the opposite direction. First, the script removes the rules from the INPUT
chain; then, after the mandatory clear-up, it removes the chains themselves (e.g., ${IPTABLES} -X BLOCK_ASN
only deletes the BLOCK_ASN
chain if it no longer contains any rules). After running teardown
, no traces of the script exist any longer in the packet filter.
The core functionality, besides multilevel capability, lies in the filter rules for discovering the potential attackers' IP addresses. To make the script as flexible as possible, the filter rules are arbitrary calls for displaying and filtering logfiles, as well as regular expressions for digging the IP addresses out of the log entries.
Basically, you could call other DIY scripts that output the appropriate IP addresses line by line. The filters then run within a subshell later. For the call, it is necessary to mask these accordingly. Using an example of two simple log entries from the /var/log/secure
file on a Gentoo system, the next section explains the approach outlined in Listing 2, in which you create one or more rules of this type for each logfile with relevant information.
Listing 2
Log Entry Examples
# Feb 22 08:00:00 hostname sshd[1234]: Failed keyboard-interactive/pam for root from 1.0.0.1 port 4321 ssh2 # Feb 22 8:00:10 AM hostname sshd[1234]: Failed keyboard-interactive/pam for invalid user mysql from 1.0.0.1 port 4322 ssh2 FILTER=' cat /var/log/secure | awk '\''$5~/^ssh/ && $6~/^Failed/ && $7~/^keyboard-interactive/ && $9~/[^invalid]/ {print $11}'\'' cat /var/log/secure | awk '\''$5~/^ssh/ && $6~/^Failed/ && $7~/^keyboard-interactive/ && $9~/[invalid]/ {print $13}'\'' '
The two entries in the logfile are based on the format of the software deployed. They can vary from system to system; therefore, it is important to test the filter expression thoroughly in advance. The example shows that the SSH daemon can generate different log entries. In the first case, the login for an existing user was entered for root on the system. The second entry also notes that the user ID mysql does not exist on the system. To prevent the filter from extracting incorrect values, the filter rule should be as precise as possible.
The cat
command writes the contents of the logfile to the standard output channel. With the help of awk
, you can check the individual fields (the default delimiter is the space character) in each line. The terms ssh
, Failed
, and keyboard-interactive
clearly identify a failed login attempt using SSH. However, the IP address occurs in the first entry in field $11
, and in the second entry in field $13
; thus, you will want to check field $9
, too. If this starts with a string that is not equal to invalid
, the first filter delivers a result; otherwise, the IP is found two fields to the right and extracted by the second filter.
« Previous 1 2 3 Next »
Buy this article as PDF
(incl. VAT)
Buy ADMIN Magazine
Subscribe to our ADMIN Newsletters
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Most Popular
Support Our Work
ADMIN content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.