Bad Actors

Dennis Mellican
5 min readOct 27, 2019

Use Slack to show your Systems are constantly Under Siege by Bad Actors like “Steven Seagal”.

This article does not refer to individuals who can’t act. Instead, refers to someone who attempts to infiltrate a system with malicious intent.

A common attack vector is to brute force their way in by guessing an account password systematically. There are various ways to deploy countermeasures, such as Intrusion Detection Systems or fail2ban which are both effective.

Their attempts to probe your network can be made highly visible using TCP wrappers and integrating it with your instant messaging system, in this case Slack. Your stakeholders suddenly become aware that systems are constantly under siege (pun intended) and your security regime and policies are there for a very good reason.

In this example (figure 1), an incoming ssh connection is intercepted by TCP wrappers. The incoming IP address is geographically located and based on a whitelist of allowed countries, are permitted to establish the connection. You could also use fail2ban to filter by geolocation, however this method using TCP wrappers makes unauthorized sshd connections highly visible.

If an IP address is deemed to be from an unauthorized country, the information is broadcasted to a #bad_actors Slack channel in a fun but serious way. The bad actor is actually named after a random Hollywood actor that may have won a Razzie.

Figure 1: #bad_actors Slack channel is also streamed live at

Further action can be warranted to update a network access control list or firewall to drop or throttle IP address packets from known offenders. You could use this method in conjunction with fail2ban to automatically throttle or ban the bad actor.

Although a bad actor can easily circumvent this countermeasure by using a VPN or another Geographic location for entry, this is simply an extra level of protection.

How to log these “bad actors”

Figure 2 below shows how TCP wrappers and a bash (or python) script to filter connections based on the Geographic location can be used to log these events and ban repeat offenders:

Figure 2: The Russian version of David Caruso is denied access and the event is logged.

The following instructions are for Ubuntu.

Check your system, in particular ssh supports tcp wrappers before commencing:

sudo ldd /usr/sbin/sshd | grep libwrap

Install geoip databases:

sudo apt-get install geoip-bin geoip-database -y

Test it:


Create a /usr/local/bin/ file:

#!/bin/bashif [ $# -ne 1 ]; then
echo "Usage: `basename $0` <ip>" 1>&2
exit 0 # return true in case of config issue
slacksend() {
# Send a Slack message to a channel# Get your Slack webhook url here: #
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"$1\"}" \$SLACK_WEBHOOK
# UPPERCASE space-separated country codes to ACCEPT
COUNTRY=$(/usr/bin/geoiplookup $1 | awk -F ": " '{ print $2 }' | awk -F "," '{ print $1 }' | head -n 1)
[[ $COUNTRY = "IP Address not found" || $COUNTRIES_WHITELIST =~ $COUNTRY ]] && RESPONSE="ALLOW" || RESPONSE="DENY"if [ $RESPONSE = "ALLOW" ]; then
logger "$RESPONSE sshd connection from $1 ($COUNTRY)"
exit 0
logger "$RESPONSE sshd connection from $1 ($COUNTRY)"
slacksend "$RESPONSE sshd connection from $1 ($COUNTRY)"
exit 1

If you are using Slack as your instant messaging system, start by setting up an incoming webhook integration in your Slack team. Copy the webhook URL and replace SLACK_WEBHOOK variable with that URL.

Update the COUNTRIES_WHITELIST variable using the two character abbreviation. For example, US is United States of America, and AU is Australia.

Make it executable:

sudo chmod u+x /usr/local/bin/

Add this line to /etc/hosts.deny:

sshd: ALL

Add this line to /etc/hosts.allow:

sshd: ALL: aclexec /usr/local/bin/ %a

Test it:

sudo /usr/local/bin/

The Geo IP address database requires regular database updates. Set up a cron job to refresh the Geo IP database by creating a /etc/cron.weekly/update-geoip file:

curl --silent -o /tmp/GeoIP.dat $URL
if [ -f GeoIP.dat ]; then
rm -f /usr/share/GeoIP/GeoIP.dat
mv -f GeoIP.dat /usr/share/GeoIP/GeoIP.dat
echo "The GeoIP library could not be downloaded and updated"

Make it executable too:

sudo chmod u+x /etc/cron.weekly/update-geoip

In a short time (yes, within minutes), you should start seeing these bad actors in Slack and in /var/log/syslog.

If you want a similar Slack message as seen in figure 1, with an actual random bad actor name and the country flag of the IP address location, you will need the Python slack library and call this script:

#!/usr/bin/env python3
'''\ -c "#channel" -m "message to send" [-u "First Lastname" -e ":emoji:"]
'''from slacker import Slacker
import re
import getopt
import sys
import random
opts, args = getopt.getopt(sys.argv[1:],"hc:m:e:u:",
except getopt.GetoptError:
message = None
channel = '#bad_actors'
emoji = None
username = None
for opt, arg in opts:
if opt == '-h':
elif opt in ("-c", "--channel"):
channel = arg
elif opt in ("-m", "--message"):
message = arg
elif opt in ("-e", "--emoji"):
emoji = arg
elif opt in ("-u", "--username"):
username = arg
if message is None:
if username is None:
bad_actors = [ "Jean-Claude Van Damme",
"Adam Sandler",
"Nicolas Cage",
"Chuck Norris",
"Brendan Fraser",
"Rob Schneider",
"David Caruso",
"Vin Diesel",
"William Shatner",
"Steven Seagal"
username = random.choice(bad_actors)
# Get your API token here:
slack = Slacker('YOUR_API_TOKEN')
if emoji is None:
# Based on the Country of origin, the Slack icon will be the flag
country = re.findall('\(.*?\)',message)[-1]
country = "None"
emoji=":flag-"+country[country.find("(")+1:country.find(")")].lower()+":"if len(emoji) > 9:
# Send a message to the channel
try:, message, username=username, as_user='false',
e = sys.exc_info()[0]
print("[ERROR] Something went wrong!",e)
raise SystemExit(1)

Call the script, stating the channel to send to and the message must contain the two letter country code identified within brackets: -c "#bad_actors" -m "Bad acting for some reason, advancing careers (KP)"

And the result in the #bad_actors Slack channel:

This article was originally published on LinkedIn: