Introduction
This blog just shares and explains the function of a script I created for *nux based systems that can be used to quickly block and unblock ports on your firewall. It works with both IPTables and FirewallD. It can also be easily configured to work with a cron and ran at key times.
There are several reasons why you might want to quickly block a port on your firewall.
- Maybe you suspect someone is intruding or violating your system.
- Maybe you want to leave your computer running (it’s crunching data for you), but for security reasons you want to close all of the ports when you’re not around (such as after work hours or over the weekend).
The Goods
At the present time, the script supports blocking inbound tcp and udp ports. It also supports blocking port ranges.
Simply copy this into a script file which I called blockport.sh (don’t forget to make it executable) and place it in your path:
#!/bin/sh
# Name: blockport.sh
# Author: Chris Caron <lead2gold@gmail.com>
# Date: Jul 17th, 2015
# Description: A simple script that blocks and unblocks outbound traffic
# The intent is to set up a cron that will block stuff at night
# and then unblock it in the morning every day preventing
# any unnecessary things from making outbound requests.
#
# Dependencies: sed, logger, grep, and iptables
#
# This script must run as root!
SCRIPTNAME=$(basename $0)
IPTABLES_BIN=iptables
IPTABLES_PRINT=echo
# Set the command to what you want to actually do
# some scripts explicitly reference the above
IPTABLES=$IPTABLES_PRINT
IPTABLES=$IPTABLES_BIN
# Default Line Numbers (where inserts will take place
IPTABLES_OUTPUT_LINE=${IPTABLES_OUTPUT_LINE:=1}
IPTABLES_INPUT_LINE=${IPTABLES_INPUT_LINE:=1}
show_syntax () {
echo "Syntax: \$> $SCRIPTNAME <Action> [Options]"
echo " Actions:"
echo " -l"
echo " --list List the ports blocked by this script."
echo
echo " -bo PORT"
echo " --block-outbound=PORT Blocks outbound ports (both udp and tcp). You can"
echo " use space or comma to delimite multiple ports"
echo " -uo PORT"
echo " --unblock-outbound=PORT Unblocks outbound ports (both udp and tcp)"
echo " previously blocked. You can use space or comma to"
echo " delimit multiple ports"
echo " -bi PORT"
echo " --block-inbound=PORT Blocks inbound ports (both udp and tcp). You can"
echo " use space or comma to delimite multiple ports"
echo " -ui PORT"
echo " --unblock-inbound=PORT Unblocks inbound ports (both udp and tcp)"
echo " previously blocked. You can use space or comma to"
echo " delimit multiple ports"
echo ""
}
clean_ports(){
# A Simple function that cleans up port ranges
local PORTS=$(echo $1 | sed -e 's/[, \\/]\+/ /g' -e 's/[;-]\+/:/' \
-e 's/[^:0-9 ]//g' -e 's/^[^0-9]\+//g' -e 's/[^0-9]\+$//g')
[ -z "$PORTS" ] && return 1
echo "$PORTS"
return 0
}
indexes() {
# Possible types are OUTPUT, INPUT, and FORWARD
local TYPE=$1
# A simple function that returns the index(es) of a iptable
# entry (if it's blocked or not).
local PORTS=$(clean_ports $2)
[ $? -ne 0 ] && return 1
local INDEXES=""
# This magical line was constructed off of what i learned here:
# http://unix.stackexchange.com/questions/129087/grep-log-and-get-text-between-log-delimiters
# It extracts the DROP lines we created in the OUTPUT Chain
for PORT in $PORTS; do
INDEX=$($IPTABLES_BIN -nL --line-numbers | \
grep -a -zPo "(\n?Chain $TYPE [^\n]*\n)\K(.|\n)+?[^\n][^Cc](.|\n)+?(?=\nChain [^\n]*\n)" | \
egrep -a 'DROP' | egrep '^[0-9]' | egrep " dpts?:$PORT[ \t]*\$" | \
sed -e 's/^[ \t]*\([0-9]\+\)[^0-9].*/\1/g')
INDEXES="$INDEXES $INDEX"
done
[ -z "$INDEXES" ] && return 1
# Sort the INDEXES (largest # to smallest) because we want to
# process the list backwards
INDEXES=$(echo $INDEXES | tr -s '[:space:]' '\n' | sort -n -r | uniq)
echo $INDEXES
return 0
}
unblock() {
# Possible types are OUTPUT, INPUT, and FORWARD
local TYPE=$1
# A simple function that returns the index(es) of a iptable
# entry (if it's blocked or not).
local PORTS=$2
# Defaults
[ -z "$TYPE" ] && TYPE=INPUT
# Stores the indexes (if set)
local INDEXES="$(indexes $TYPE $PORTS)"
[ -z "$INDEXES" ] && return 0
# Sort the INDEXES (largest # to smallest) because we want to
# process the list backwards
INDEXES=$(echo $INDEXES | tr -s '[:space:]' '\n' | sort -n -r | uniq)
for INDEX in $INDEXES; do
$IPTABLES -D $TYPE $INDEX
done
logger "INFO - blockport.sh: Unblocked $TYPE $PORTS"
return 0
}
block(){
# Possible types are OUTPUT, INPUT, and FORWARD
local TYPE=$1
# A simple function that returns the index(es) of a iptable
# entry (if it's blocked or not).
local PORTS=$(clean_ports $2)
[ $? -ne 0 ] && return 1
# Defaults
[ -z "$TYPE" ] && TYPE=INPUT
# If indexes already exist, then we don't have to do anything
local INDEXES="$(indexes $TYPE $PORTS)"
[ ! -z "$INDEXES" ] && return 0
local LINE=$(eval "echo \$IPTABLES_${TYPE}_LINE")
[ -z $LINE ] && LINE=1
for PORT in $PORTS; do
$IPTABLES -I $TYPE $LINE -p tcp -s 0/0 --destination-port $PORT -j DROP
$IPTABLES -I $TYPE $LINE -p udp -s 0/0 --destination-port $PORT -j DROP
done
logger "INFO - blockport.sh: Blocked $TYPE $PORTS"
return 0
}
list() {
echo
for TYPE in "INPUT" "OUTPUT" ; do
echo "Listing $TYPE Ports Blocked:"
$IPTABLES_BIN -nL --line-numbers | \
grep -a -zPo "(\n?Chain $TYPE [^\n]*\n)\K(.|\n)+?[^\n][^Cc](.|\n)+?(?=\nChain [^\n]*\n)" | \
egrep -a 'DROP' | egrep '^[0-9]' | egrep " dpts?:[0-9:]+[ \t]*\$"
echo
done
return 0
}
##########################################################
## Main ##
##########################################################
PATH=/bin:/sbin:/usr/bin:/usr/sbin
if [ $(whoami) != "root" ]; then
echo "Error: you must be root to execute this script."
fi
ACTION="x"
RETVAL=0
while : ; do
case $1 in
-l) list; exit 0 ;;
--list) list; exit 0 ;;
-bo) ACTION='bo';
block "OUTPUT" $2;
[ $? -ne 0 ] && RETVAL=1
shift; shift ;;
--block-outbound) ACTION='bo';
block "OUTPUT" $(echo $1 | sed -e 's/--block-outbound=//g' -e "s/'//g" -e 's/\"//g')
[ $? -ne 0 ] && RETVAL=1
shift ;;
-uo) ACTION='uo';
unblock "OUTPUT" $2;
[ $? -ne 0 ] && RETVAL=1
shift; shift ;;
--unblock-outbound) ACTION='uo';
unblock "OUTPUT" $(echo $1 | sed -e 's/--unblock-outbound=//g' -e "s/'//g" -e 's/\"//g')
[ $? -ne 0 ] && RETVAL=1
shift ;;
-bi) ACTION='bi';
block "INPUT" $2;
[ $? -ne 0 ] && RETVAL=1
shift; shift ;;
--block-inbound) ACTION='bi';
block "INPUT" $(echo $1 | sed -e 's/--block-inbound=//g' -e "s/'//g" -e 's/\"//g')
[ $? -ne 0 ] && RETVAL=1
shift ;;
-ui) ACTION='ui';
unblock "INPUT" $2;
[ $? -ne 0 ] && RETVAL=1
shift; shift ;;
--unblock-inbound) ACTION='ui';
unblock "INPUT" $(echo $1 | sed -e 's/--unblock-inbound=//g' -e "s/'//g" -e 's/\"//g')
[ $? -ne 0 ] && RETVAL=1
shift ;;
-h) show_syntax ; exit 0 ;;
--help) show_syntax ; exit 0 ;;
*) if [ -z "$1" ]; then break; fi
echo "[error] Invalid option '$1' specified; see --help (-h) for more info."
exit 1
;;
esac
done
if [ $ACTION == "x" ]; then
show_syntax
exit 1
fi
exit $RETVAL
Again, I state: this script must be ran as root (because it wraps IPTables). So stick sudo in front of it’s calls if running as a regular user (assuming you’re set up with sudoer’s privileges).
Syntax
Syntax: $> blockport.sh [Options]
Actions:
-l
--list List the ports blocked by this script.
-bo PORT
--block-outbound=PORT Blocks outbound ports (both udp and tcp). You can
use space or comma to delimit multiple ports
-uo PORT
--unblock-outbound=PORT Unblocks outbound ports (both udp and tcp)
previously blocked. You can use space or comma to
delimit multiple ports
-bi PORT
--block-inbound=PORT Blocks inbound ports (both udp and tcp). You can
use space or comma to delimit multiple ports
-ui PORT
--unblock-inbound=PORT Unblocks inbound ports (both udp and tcp)
previously blocked. You can use space or comma to
delimit multiple ports
Demo
Here is a simple example that just blocks 2 ports. We can chain more than one port by placing a comma in between each one. It is also valid to keep using one switch after another (they’ll be executed in order).
# Here is how easy it is to use; first we'll block (inbound) ports 80 and 443
blockport.sh -bi 80,443
# This is also valid syntax:
# blockport.sh -bi 80 -bi 443
# We know they're blocked now, but we can have a look anyway:
blockport.sh -l
# The output will look like this:
# Listing INPUT Ports Blocked:
# 1 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:443
# 2 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
# 3 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:80
# 4 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
#
# Listing OUTPUT Ports Blocked:
#
# We can reverse this by typing:
blockport.sh -ui 80,443
# This is also valid syntax:
# blockport.sh -ui 80 -ui 443
You can do ranges too; just use the colon (:) or hyphen (-). In the example below, we block a range of outgoing traffic from a system:
# Below blocks outbound ports 20, 21, and all ports (and including) 8000-8500.
blockport.sh -bo 20,21,8000-8500
# This is also valid syntax:
# blockport.sh -bo 20 -bo 21 -bo 8000-8500
# We know they're blocked now, but we can have a look anyway:
blockport.sh -l
# The output will look like this:
# Listing INPUT Ports Blocked:
#
# Listing OUTPUT Ports Blocked:
# 1 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpts:8000:8500
# 2 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpts:8000:8500
# 3 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:21
# 4 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:21
# 5 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:20
# 6 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:20
# We can reverse this by typing:
blockport.sh -uo 20,21,8000-8500
# This is also valid syntax:
# blockport.sh -uo 20 -uo 21 -uo 8000-8500
Note: You can only unblock what you block. Here is an example of what I mean:
# Blocking a range:
blockport.sh -bo 8000-8500
# You CAN NOT just unblock a port from it (this will not work):
blockport.sh -uo 8400
# Similarly, you can not block individual ports and then try to unblock them
# as a range:
blockport.sh -bo 20,21
# You CAN NOT just unblock this as a range (this will not work):
blockport.sh -uo 20-21
Caution
This script is intended to be an instant port blocker. It intentionally destroys any pre-established connections utilizing the port marked. Keep this in mind so that you don’t block yourself out of your own Server or VPS. Hence DON’T CLOSE PORT 22 unless you know what and why you’re doing it. You have been warned! 🙂
Use Cases
Suppose you want to deny access out of a server you host for your company after hours, you could create a cron like this:
# Block defined outbound ports at around 5:30pm every evening on Weekdays
# (Mon - Fri)
30 17 * * 1-5 /root/bin/blockport.sh -bo 80,443,22,21 &>/dev/null
# Unblock the defined ports every morning at 7am on Weekdays
# (Mon - Fri) keeping them blocked over the weekends
0 7 * * 1-5 /root/bin/blockport.sh -uo 80,443,22,20,21 &>/dev/null
Alternatively, maybe something looked bad in /var/log/messages or /var/log/audit/audit.log to you and you simply just want to immediately block the port.
Credit
If you like what you see and wish to copy and paste this HOWTO, please reference back to this blog post at the very least. It’s really all I ask.