It is harder to kill a whisper than even a shouted calumny.”
~Mary Stewart

Brutus- Brutus Server

Google+ Pinterest LinkedIn Tumblr +

The Brutus server is essentially any machine running Linux that has Portsentry installed.  In the case of our reference diagram, let’s assume we’ve made the Web server the Brutus server, and the EMail server the Client. You could of course make Brutus run on its own dedicated host as well. It’s helpful to install postfix on this machine so that you can receive emailed status reports.

On Ubuntu you can install Portsentry with “apt get portsentry”.

 The project home page is here at SourceForge: https://sourceforge.net/projects/sentrytools/

Once Portsentry has been installed, place this portsentry.conf file in /etc/portsentry .  This is what shapes Portsentry’s behavior and determines what to run in the event a port “touch” is detected. 

portsentry.conf
TCP_PORTS="1,11,15,79,111,119,143,540,635,1080,1524,2000,5742,6667,12345,12346,20034,27665,31337,32771,32772,32773,32774,40421,49724,54320"
UDP_PORTS="1,7,9,69,161,162,513,635,640,641,700,37444,34555,31335,32770,32771,32772,32773,32774,31337,54321"

ADVANCED_PORTS_TCP="65535"
ADVANCED_PORTS_UDP="65535"
ADVANCED_EXCLUDE_TCP="22"
ADVANCED_EXCLUDE_UDP=""

IGNORE_FILE="/etc/portsentry/portsentry.ignore"
HISTORY_FILE="/etc/portsentry/portsentry.history"
BLOCKED_FILE="/etc/portsentry/portsentry.blocked"

RESOLVE_HOST = "0"
SCAN_TRIGGER = "0"
KILL_RUN_CMD_FIRST = "0"

BLOCK_UDP="1"
BLOCK_TCP="1"

# target=source IP, port=touch detected on this port, mode=atcp,tcp,audp,udp
# only target's used for blocking, the rest is for logging
KILL_ROUTE="/etc/portsentry/instructrouter.sh $TARGET$ $PORT$ $MODE$"

In the /etc/portsentry folder, you can place this instructrouter.sh script.  The heart of Brutus, this is the script called by portsentry.conf above that instructs your firewall to null-route the offending external IP address (“null-route” means “the route to take to get to this IP is.. yourself.. there is no other route”, effectively dropping this IP in the trashcan.)

In order for the portsentry process to SSH into your firewall, you need to create a public-private keypair for the root account on your Brutus server.  You can do it with the commands at right. You don’t need a key larger than 1024 bits as it’s being used in an internal control channel. 

sudo su – 
ssh-keygen -t rsa -b 1024

In the /root/.ssh folder, copy the contents of id-rsa.pub to the Firewall’s admin user public key field, per the “Firewall” article instructions.

This script is also called via root’s crontab as per the string at right.

45 23 * * * /etc/portsentry/instructrouter.sh r

For firewalls other than PFSense, the command to actually execute the block will vary based on the operating system but you only have to test and change a single line (about line #89 in the * section).

instructrouter.sh
#!/bin/bash

# designed for PFSense, meant to work with apt package 'portsentry'
# this script works by placing hostile external IP's into a null-route in the router's manual table.
# note that it'll only block scanning / hostile IP's externally, you can still reach them
#       from the inside, ie, the null-route is only effective for inbound requets to WAN

# target=source IP, port=touch detected on port, mode=atcp,tcp,audp,udp
#KILL_ROUTE="/etc/portsentry/instructrouter.sh $TARGET$ $PORT$ $MODE$"
# note : root's public key needs to be in pfsense user 'admin'
# $hoursold = after these hours, unblock IP address
# usage:
#       instructrouter.sh r
#       instructrouter.sh 1.2.3.4
# updated 100517 to use routing table instead of reconstructing config.xml, infinitely superior
#       copy ssh key into pfsense admin account

homedir=/etc/portsentry
blocklog=$homedir/blocklog.log
curdate=$(date +%m%d%y.%H%M%S)
curdateepoch=$(date +%s) # epoch time used for numerical comparison to find old records
tmpfolder="/tmp/$(basename $0).$$"
remuser="root" #root is actually called admin on pfsense
fwrouter=192.168.1.1
binlogger=/usr/bin/logger
hoursold=48
alertemail="[email protected]"

function statusis {
        echo "status : $1"
        $binlogger -t PORTSENTRY: $1
}

function mailout {
        grep PORTSENTRY /tmp/log/syslog | tail -n10 > $tmpfolder/sysout
        mail -s BrutusError $alertemail < $tmpfolder/sysout
}

function traperr {
        if [ $? -ne 0 ]; then
                errdata=$1
                statusis "Error found, $errdata"
                mailout
                exit 1
        fi
}

function cleanup {
        rm -rf $tmpfolder
}

mkdir $tmpfolder
case $1 in
        "")
                echo "Help : append with ip address to block or 'r' to remove stale"
                statusis "exiting due to null parameter"
                mailout
                cleanup
                exit 1
        ;;
        r)
                if [ -n "$2" ]; then
                        rmip="$2"
                        # remove the IP provided via command line
                        ssh -f [email protected]$fwrouter "route del $rmip" 2>&1 > /dev/null
                        cleanup
                        exit 0
                else
                        if [ ! -f $blocklog ] || [ -z "$(cat $blocklog)" ]; then
                                statusis "Nothing in the blocklog, exiting"
                                exit 0
                        fi

                        # figure out from $blocklog which IP's are more than $hoursold and remove from fw
                        while read blockline; do  #blockline format : ip blockdate blockepoch
                                lesshrsold=$(date --date "now -$hoursold hours" +%s)
                                if [ $(echo $blockline | cut -d " " -f3) -lt $lesshrsold ]; then
                                        rmip=$(echo $blockline | cut -d " " -f1)
                                        # the -f is crucial lest the script abort after an ssh error if route missing
                                        ssh -f [email protected]$fwrouter "route del $rmip" 2>&1 > /dev/null
                                fi
                        done < $blocklog
                        cleanup
                        exit 0
                fi
        ;;
        *)
                iptoblock=$1
                ssh [email protected]$fwrouter "route add $iptoblock 127.0.0.1 -blackhole"
                if [ $? -ne 0 ]; then
                        statusis "can't add IP to $fwrouter, exiting"
                        exit 1
                fi
                echo "$iptoblock $curdate $curdateepoch" >> $blocklog
                cleanup
                exit 0
        ;;
esac

The fwreport.sh script can go in /etc/portsentry too.  This is executed by hand whenever you want to:

    a) confirm root’s key is working on the firewall, and 

    b) provide a current list of IP’s being blocked by Brutus.

 

fwreport.sh
#!/bin/bash
# This script intended to work with PFSense
# the root user's public key needs to be in the router's authorized_keys
# or assigned to the PFSense admin user

remuser="root"
fwrouter=192.168.1.1

echo -n "IP's currently in FW table: "
ssh [email protected]$fwrouter "netstat -r -n | grep 127.0.0.1 | grep UGHSB" > /tmp/reporttmp
cat /tmp/reporttmp | wc -l
cat /tmp/reporttmp
rm /tmp/reporttmp

blockreport.sh is also placed in /etc/portsentry, and it’s executed via crontab every day at about 11:55p (before midnight) so as to email you with a daily status report.  It also performs some maintenance, purging records older than a few days.

Root’s crontab line looks like the command at right:

 47 23 * * * /etc/portsentry/blockreport.sh

blockreport.sh
#!/bin/bash
# generate report to show blocks within $withinlast hours

tmpfolder="/tmp/$(basename $0).$$"
homedir=/etc/portsentry
histfile=$homedir/portsentry.history
withinlast=24
# think days you want +2 including today, must be 1 day greater than purge value in the manager
purgeafterday=4
[email protected]

mkdir $tmpfolder

#truncate history file first, less to deal with overall
cat $histfile | while read lineitem; do
        if [ $(echo "$lineitem" | awk '{ print $1 }') -ge $(date --date "now -$purgeafterday days" +%s) ]; then
                echo "$lineitem" >> $tmpfolder/keeplist
        fi
done
mv $tmpfolder/keeplist $histfile

#from the cleaned up list extract current data
cat $histfile | while read lineitem; do
        if [ $(echo "$lineitem" | awk '{ print $1 }') -ge $(date --date "now -$withinlast hours" +%s) ]; then
                echo "$lineitem" >> $tmpfolder/linelist
        fi
done

linecount=$(cat $tmpfolder/linelist | wc -l)

#port statistics
cat $tmpfolder/linelist | awk '{ print $8 }' > $tmpfolder/portlist
cat $tmpfolder/portlist | sort | uniq > $tmpfolder/uniqueports

cat $tmpfolder/uniqueports | while read lineitem; do
        portcount=$(cat $tmpfolder/portlist | grep $lineitem | wc -l)
        svcname="$(grep -w "$lineitem/tcp" /etc/services | awk '{ print $1 }')"
        echo "$portcount        $lineitem       $svcname" >> $tmpfolder/portout
done

echo "Count     Port    Service Name" >> $tmpfolder/portreport
echo "-------------------------------" >> $tmpfolder/portreport
cat $tmpfolder/portout | sort -nr >> $tmpfolder/portreport

mail -s "Brutus report - $linecount IP's blocked" $emailto < $tmpfolder/portreport

rm -rf $tmpfolder

To test, google “online port scan”, you’ll see offerings by MXToolbox and others – use their services to scan your external IP address and watch your routing table (or run fwreport.sh) and you should see the source IP get dropped very quickly!

After all this is done, Portsentry is now able to instruct your firewall to drop inbound portscans automatically, resulting in a network that’s MUCH harder to map by attackers.

This is the heart of Brutus, you could be done if you have a home network with no servers or have a single server behind a firewall in your DMZ that provides service.  If you have more than one server you need to protect, proceed to the next article for Brutus client configuration.

Share.

Leave A Reply

Secured By miniOrange