iptables
iptables
EC2 Menu
EC2 Menu
Use iptables to block ip addresses or ports
Blocking IP addresses with ipset and iptables
This gives ability to blacklisted IP addresses, removing lists of “deny from” commands in the .htacess file. The tables are for single addresses, not CIDR ranges. An EC2 instance Security Groups has limits on how many IP addresses you can block, and are difficult to manage and have performance issues.
The ipset/iptables system is at kernel level, so it is fast.
Blocking IPV4 Addresses
This makes use of github.com/kravietz/blacklist-scripts
Make a snapshot backup of your instance volume first.
For Linux 2023, install these packages: (curl should already be installed)
dnf -y install ipset iptables dnf -y install curl
Place this script with root ownership, ec2-user group, chmod 775 in /home/ec2-user:
Where you see URLS=”$URLS https://YOURDOMAIN/block.txt” you can uncomment and use your own domain blacklist if desired.
cd /home/ec2-user vi blacklist.sh #!/bin/sh # IP blacklisting script for Linux servers # Pawel Krawczyk 2014-2015 # documentation https://github.com/kravietz/blacklist-scripts # iptables logging limit LIMIT="10/minute" # try to load config file # it should contain one blacklist URL per line config_file="/etc/ip-blacklist.conf" if [ -f "${config_file}" ]; then source ${config_file} else # if no config file is available, load default set of blacklists # URLs for further blocklists are appended using the classical # shell syntax: "$URLS new_url" # Emerging Threats lists offensive IPs such as botnet command servers URLS="http://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt" # Blocklist.de collects reports from fail2ban probes, listing password brute-forces, scanners and other offenders URLS="$URLS https://www.blocklist.de/downloads/export-ips_all.txt" # YOUR OWN BLACKLIST (uncomment as needed) where block.txt is your own list, but not with CIDR ranges - you can keep those as needed in /var/www/html/.htaccess # URLS="$URLS https://YOURDOMAIN/block.txt" # badips.com, from score 2 up # URLS="$URLS http://www.badips.com/get/list/ssh/2" # iblocklist.com is also supported # URLS="$URLS http://list.iblocklist.com/?list=srzondksmjuwsvmgdbhi&fileformat=p2p&archiveformat=gz&username=USERNAMEx$&pin=PIN" fi link_set () { if [ "$3" = "log" ]; then iptables -A "$1" -m set --match-set "$2" src,dst -m limit --limit "$LIMIT" -j LOG --log-prefix "BLOCK $2 " fi iptables -A "$1" -m set --match-set "$2" src -j DROP iptables -A "$1" -m set --match-set "$2" dst -j DROP } # This is how it will look like on the server # Chain blocklists (2 references) # pkts bytes target prot opt in out source destination # 0 0 LOG all -- * * 0.0.0.0/0 0.0.0.0/0 match-set manual-blacklist src,dst limit: avg 10/min burst 5 LOG flags 0 level 4 prefix "BLOCK manual-blacklist " # 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set manual-blacklist src,dst # 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set rules.emergingthreats src # 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set rules.emergingthreats dst # 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set www.blocklist.de src # 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set www.blocklist.de dst # 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set www.badips.com src # 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set www.badips.com dst blocklist_chain_name=blocklists # check for dependencies - ipset and curl if [ -z "$(which ipset 2>/dev/null)" ]; then echo "Cannot find ipset" echo "Run "apt-get install ipset" (Debian/Ubuntu) or "yum install ipset" (RedHat/CentOS/Fedora) or "opkg install ipset" (OpenWRT/LEDE)" exit 1 fi if [ -z "$(which curl 2>/dev/null)" ]; then echo "Cannot find curl" echo "Run "apt-get install curl" (Debian/Ubuntu) or "yum install curl" (RedHat/CentOS/Fedora) or "opkg install curl" (OpenWRT/LEDE)" exit 1 fi # check if we are on OpenWRT if [ "$(which uci 2>/dev/null)" ]; then # we're on OpenWRT wan_iface=pppoe-wan IN_OPT="-i $wan_iface" INPUT=input_rule FORWARD=forwarding_rule COMPRESS_OPT="" else COMPRESS_OPT="--compressed" INPUT=INPUT FORWARD=FORWARD fi # create main blocklists chain if ! iptables -nL | grep -q "Chain ${blocklist_chain_name}"; then iptables -N ${blocklist_chain_name} fi # inject references to blocklist in the beginning of input and forward chains if ! iptables -nL ${INPUT} | grep -q ${blocklist_chain_name}; then iptables -I ${INPUT} 1 ${IN_OPT} -j ${blocklist_chain_name} fi if ! iptables -nL ${FORWARD} | grep -q ${blocklist_chain_name}; then iptables -I ${FORWARD} 1 ${IN_OPT} -j ${blocklist_chain_name} fi # flush the chain referencing blacklists, they will be restored in a second iptables -F ${blocklist_chain_name} # create the "manual" blacklist set # this can be populated manually using ipset command: # ipset add manual-blacklist a.b.c.d set_name="manual-blacklist" if ! ipset list | grep -q "Name: ${set_name}"; then ipset create "${set_name}" hash:net fi link_set "${blocklist_chain_name}" "${set_name}" "$1" # download and process the dynamic blacklists for url in $URLS do # initialize temp files unsorted_blocklist=$(mktemp) sorted_blocklist=$(mktemp) new_set_file=$(mktemp) headers=$(mktemp) # download the blocklist set_name=$(echo "$url" | awk -F/ '{print substr($3,0,21);}') # set name is derived from source URL hostname curl -L -v -s ${COMPRESS_OPT} -k "$url" >"${unsorted_blocklist}" 2>"${headers}" # this is required for blocklist.de that sends compressed content regardless of asked or not if [ -z "$COMPRESS_OPT" ]; then if grep -qi 'content-encoding: gzip' "${headers}"; then mv "${unsorted_blocklist}" "${unsorted_blocklist}.gz" gzip -d "${unsorted_blocklist}.gz" fi fi # autodetect iblocklist.com format as it needs additional conversion if echo "${url}" | grep -q 'iblocklist.com'; then if [ -f /etc/range2cidr.awk ]; then mv "${unsorted_blocklist}" "${unsorted_blocklist}.gz" gzip -d "${unsorted_blocklist}.gz" awk_tmp=$(mktemp) awk -f /etc/range2cidr.awk <"${unsorted_blocklist}" >"${awk_tmp}" mv "${awk_tmp}" "${unsorted_blocklist}" else echo "/etc/range2cidr.awk script not found, cannot process ${unsorted_blocklist}, skipping" continue fi fi sort -u <"${unsorted_blocklist}" | egrep "^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}(/[0-9]{1,2})?$" >"${sorted_blocklist}" # calculate performance parameters for the new set if [ "${RANDOM}" ]; then # bash tmp_set_name="tmp_${RANDOM}" else # non-bash tmp_set_name="tmp_$$" fi new_list_size=$(wc -l "${sorted_blocklist}" | awk '{print $1;}' ) hash_size=$(expr $new_list_size / 2) if ! ipset -q list ${set_name} >/dev/null ; then ipset create ${set_name} hash:net family inet fi # start writing new set file echo "create ${tmp_set_name} hash:net family inet hashsize ${hash_size} maxelem ${new_list_size}" >>"${new_set_file}" # convert list of IPs to ipset statements while read line; do echo "add ${tmp_set_name} ${line}" >>"${new_set_file}" done <"$sorted_blocklist" # replace old set with the new, temp one - this guarantees an atomic update echo "swap ${tmp_set_name} ${set_name}" >>"${new_set_file}" # clear old set (now under temp name) echo "destroy ${tmp_set_name}" >>"${new_set_file}" # actually execute the set update ipset -! -q restore < "${new_set_file}" link_set "${blocklist_chain_name}" "${set_name}" "$1" # clean up temp files rm "${unsorted_blocklist}" "${sorted_blocklist}" "${new_set_file}" "${headers}" done exit [save and exit, or simply copy the. blacklist.sh from github]
I had to comment out the line below as it no longer functions:
www.badips.com/get/list/ssh/2 as it is no longer online.
Now execute these commands:
cd /home/ec2-user chmod 775 blacklist.sh chown root blacklist.sh chgrp ec2-user blackllist.sh sh -x ./blacklist.sh
When this completes, assuming it does not exit in error or freeze, which would mean some problem analysis, you can view the results, and at various times you can look at how many IP addresses were blocked:
ipset list iptables -L -vn
To remove and redo the lists, simply sync;reboot your instance and run the script again. This will ensure all IP’s are dropped and then reconfigured. (A crontab shell script could automate.)
This is mainly here for reference, when I was working with Dovecot. It may come in handy, and can be used for any external attempts to use a port nunber.
We may also add some scripting to deal with attempts to connect to postfix. The idea is to examine /var/log/mail.log to see what kinds of patterns we can search for, then add those patterns to the script.
touch /home/ec2-user/email-drop.txt touch /home/ec2-user/email-drop.dat chmod 777 /home/ec2-user/email-drop.txt /home/ec2-user/email-drop.dat chgrp ec2-user /home/ec2-user/email-drop.txt /home/ec2-user/email-drop.dat [These data files enable an ongoing collection of IP addresses to block] vi /home/ec2-user/email-abuse.sh #!/bin/sh echo UNKNOWN EMAIL CONNECTION ATTEMPTS echo "" cd /var/log grep unknown mail.log|awk '{print $NF}'|grep -v YOUR_EC2_IP_ADDRESS|grep -v YOUR_OWN_STATIC_IP_ADDRESS|grep unknown|sort -u|awk -F[ '{print $2}'|awk -F] '{print $1}' echo "" grep unknown /var/log/mail.log|awk -F[ '{print $3}'|awk -F] '{print $1}' grep "connect from" mail.log|grep -v submission|awk -F[ '{print $3}'|awk -F] '{print $1}' exit [save and exit, then chgrp ec2-user and chmod 777] vi /home/ec2-user/email-drop.sh #!/bin/sh :> /home/ec2-user/email-drop.dat /home/ec2-user/email-abuse.sh | grep "." | sort -u -n > /home/ec2-user/email-drop.dat cat /home/ec2-user/email-drop.txt >> /home/ec2-user/email-drop.dat cat /home/ec2-user/email-drop.dat | sort -u -n > /home/ec2-user/email-drop.txt iptables -F INPUT for i in `cat /home/ec2-user/email-drop.txt` do iptables -A INPUT -p tcp --destination-port 587 -s $i -j DROP done iptables -S INPUT exit [save and exit, chgrp ec2-user and chmod 777]
We kick off the acceptance of port 587 with: iptables -I INPUT 1 -p tcp --dport 587 -j ACCEPT Then we used something like this: (I'm showing no more than 4 open connections to port 587 from one IP address. You can change the value, but keep an eye on things by testing with the openssl command: openssl s_client -crlf -quiet -starttls smtp -connect DOMAIN.COM:587 iptables -A INPUT -p tcp --syn --dport 587 -m connlimit --connlimit-above 4 -j DROP We can restrict open connections to the web pages from individual ip addresses. e.g.: iptables -A INPUT -p tcp --syn --dport 443 -m connlimit --connlimit-above 4 -j DROP iptables -A INPUT -p tcp --syn --dport 80 -m connlimit --connlimit-above 2 -j DROP We can also block CIDR ranges of addresses instead of putting them in .htaccess. For example, vi cidr.sh with content like this: #!/bin/sh ipset create cidr-set hash:net ipset add cidr-set 104.238.64.0/18 ipset add cidr-set 173.201.0.0/16 iptables -A INPUT -m set --match-set cidr-set src -j DROP [save and exit, and appropriate permissions] When you run these commands, check with iptables -L -vn You will see output including things like this: 0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:587 flags:0x17/0x02 #conn src/32 > 4 803 50704 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 flags:0x17/0x02 #conn src/32 > 4 22 1232 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 flags:0x17/0x02 #conn src/32 > 2
Note:
I now only run the blacklist.sh script and these lines:
iptables -A INPUT -p tcp --syn --dport 443 -m connlimit --connlimit-above 5 -j DROP iptables -A INPUT -p tcp --syn --dport 80 -m connlimit --connlimit-above 2 -j DROP
If using port 587 for any email inbound and passed by the EC2 Security Group, these settings are needed:
iptables -I INPUT 1 -p tcp --dport 587 -j ACCEPT iptables -A INPUT -p tcp --syn --dport 587 -m connlimit --connlimit-above 4 -j DROP [as a safety measure in crontab for each minute:] * * * * * /usr/sbin/iptables -I INPUT 1 -p tcp --dport 587 -j ACCEPT >/dev/null 2>&1
Without these lines, hackers flood the port and stop it working.