Geolock — a Perl script for Asterisk or FreePBX users to enhance security

 

Important
This is an edited version of a post that originally appeared on a blog called The Michigan Telephone Blog, which was written by a friend before he decided to stop blogging. It is reposted with his permission. Comments dated before the year 2013 were originally posted to his blog.

I created the following Perl script and have been running it for a week or so, and it seems to be working well.  The idea is that this script runs once per minute, and whenever a SIP or IAX extension is registered with your Asterisk system the script looks at the IP address that the extension is registering from, and if that address is outside your home country (the United States by default), the IP address is immediately banned using IPtables.  So, your remote extensions could be anywhere in your home country and connect to your system, but if a hacker from some other nation penetrates your system and somehow guesses one of your passwords, they will (in theory) have less than a minute to do any damage before they are banned. And once they are banned, the rightful user of that extension should still have no difficulty connecting, as long as they are not coming in from outside your home country.

For those of you that don’t have any extensions connecting from outside your home country, consider this another tool in your arsenal of defenses against intrusions.  Combine it with strong passwords, and Fail2Ban with IPtables for additional security.

NOTE THAT THIS SCRIPT IS NOT GUARANTEED TO DO ANYTHING AT ALL, other than take up space on your computer.  IT SHOULD STILL BE CONSIDERED EXPERIMENTAL until there has been more testing on it.  I believe it works properly, but have no way to do extreme testing on it to see if or how it might break. THERE IS NO WARRANTY OF ANY KIND!!!

Prerequisite: Obviously, you must have IPtables installed and functioning properly, and you must install either the Geo::IP or Geo::IP::PurePerl Perl module (do NOT install both!). You can install one of these using Webmin (using Webmin’s Others | Perl modules page), or in any other way you usually install Perl modules (e.g. CPAN). The difference between the two was explained in my original article, as follows:

… there is a Perl module called Geo::IP, which calls the GeoIP C API. If you install that API (when downloading, I’d go into the test/ directory and get the latest beta) using the directions on the linked page, and then install the Perl module (you must do it in that order, or installation of the Perl module will fail), you could run a Perl script that shows the location that your off-site extensions are coming in from. If you don’t want to install the API, or can’t figure out how (not difficult if you follow the directions), you can use the Geo::IP::PurePerl Module which is slower, but does not require the additional C library. Just so you know, GeoIP puts its data file at /usr/local/share/GeoIP/GeoIPCity.dat and they suggest that you go to http://www.maxmind.com/download/geoip/database/ every month or so to grab the latest database (the full link for the country database is currently http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz or you can get a much larger city-level database at http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz — just make sure you don’t grab a CSV version by mistake!). If you need them, there are installation instructions for the database, although they are primarily for the city-level database. If you buy an account, they’ll give you automatic updates, though I can’t imagine it would be that hard to write a script to do that (or maybe Google could help you find one, or see this message thread).

If the above is confusing to you, I’d stick with the Geo::IP::PurePerl Module. That should at least get you going. You also need the Perl Sys::Syslog module (unless you want to omit all the syslog-related instructions), though chances are you may already have that one.

Now, here is the Perl script. I called it geolock.pl (although you are free to name it whatever you want), and I put it in the /var/lib/asterisk/agi-bin/ directory and made it executable. Note this is in a code block so the long lines will overflow the column width, so you need to copy and paste this into a text editor. Also there are certain lines you will need to change, as explained below:

#!/usr/bin/perl
use strict;
use warnings;
use Geo::IP;
use Sys::Syslog;
my $gi = Geo::IP->new(GEOIP_STANDARD);
my ($ext, @peerline, @extension, $ipaddress, $country, $shellcmd);
my $flag = 0;

my @sippeers = `asterisk -rx "sip show peers" | grep -v 192.168.0. | grep ^[1-9] | grep -v "(Unspecified)" | grep / | sort -n`;
# change grep statements in above line to match your local IP range (1st grep) and first digits of extensions (2nd grep)
foreach (@sippeers) {
@peerline = split(" ");
$ipaddress = $peerline[1];
$country = $gi->country_code_by_name($ipaddress);
@extension = split("/",$peerline[0]);
$ext = $extension[0];
#    print "Extension $ext has IP address $ipaddress which is in country $countryn";
if ($country && $country ne 'US') {
$shellcmd = `iptables -D INPUT -p ALL -s $ipaddress -j DROP 2>&1`;
system("iptables -A INPUT -p ALL -s $ipaddress -j DROP");
openlog($0,'pid','user');
syslog('notice', "Banning IP address $ipaddress in $country because Asterisk SIP Extension $ext is connecting from there");
closelog;
if (index($shellcmd, "Bad rule") >= 0) {
$shellcmd = 'echo "This is an automated message - please do not reply. IP address ' . $ipaddress . ' in country ' . $country . ' was banned in iptables because Asterisk SIP extension ' . $ext . ' was connecting from there." | mail -s "IP address banned on Asterisk box" root@localhost';
system($shellcmd);
$flag = 1;
}
}
}

### INCLUDE THIS NEXT SECTION ONLY IF YOU HAVE IAX2 EXTENSIONS ###
my @iaxpeers = `asterisk -rx "iax2 show peers" | grep -v 192.168.0. | grep ^[1-9] | grep -v "(Unspecified)" | grep -v "iax2 peers" | sort -n`;
# change grep statements in above line to match your local IP range (1st grep) and first digits of extensions (2nd grep)
foreach (@iaxpeers) {
@peerline = split(" ");
$ipaddress = $peerline[1];
$country = $gi->country_code_by_name($ipaddress);
$ext = $peerline[0];
#    print "Extension $ext has IP address $ipaddress which is in country $countryn";
if ($country && $country ne 'US') {
$shellcmd = `iptables -D INPUT -p ALL -s $ipaddress -j DROP 2>&1`;
system("iptables -A INPUT -p ALL -s $ipaddress -j DROP");
openlog($0,'pid','user');
syslog('notice', "Banning IP address $ipaddress in $country because Asterisk IAX2 Extension $ext is connecting from there");
closelog;
if (index($shellcmd, "Bad rule") >= 0) {
$shellcmd = 'echo "This is an automated message - please do not reply. IP address ' . $ipaddress . ' in country ' . $country . ' was banned in iptables because Asterisk IAX2 extension ' . $ext . ' was connecting from there." | mail -s "IP address banned on Asterisk box"
root@localhost';
system($shellcmd);
$flag = 1;
}
}
}
### END OF SECTION ONLY NEEDED IF YOU HAVE IAX2 EXTENSIONS ###

if ($flag == 1) {
`asterisk -rx "restart now"`;
}
else {
openlog($0,'pid','user');
syslog('info', "Completed normally");
closelog;
}

These are the things you need to change for your local installation:

  • If you used the Geo::IP::PurePerl module then be sure to change the two references to Geo::IP to Geo::IP::PurePerl.
  • Remove the optional section for IAX2 extensions if you don’t have any of those (but keep it if you have any IAX2 extensions, even if they are only internal ones).
  • In the line(s) “if ($country && $country ne ‘US’) {” change US to the code for your home country if you are somewhere else in the world. You could also create a more expansive conditional statement here to allow multiple countries, or a more restrictive ones if you want to block an IP that doesn’t resolve to any country (I consider that a database error, but maybe you don’t). If you have installed the city-level database, you could even (in theory) test for something other than country, such as a state or province, city, time zone, ISP, etc. This line appears in the SIP section and also the optional IAX2 section, so make sure you change both lines if necessary.
  • There are two instances of root@localhost in the above script (in the SIP section and also the optional IAX2 section), which you should change to a valid e-mail address if you want to receive e-mail notifications when an IP address is banned. If you don’t want to receive such e-mail notifications, then comment out or remove those two lines in their entirety (they start with: $shellcmd = ‘echo “This is an automated message …) and also remove the following system($shellcmd); line(s).

The following apply to the two lines that begin with "my @sippeers” and, in the optional IAX2 section, “my @iaxpeers”:

  • Change “grep -v 192.168.0.” to a regular expression or pattern that will match it IP address of all extensions on your local network.  The pattern as shown works if all your local extensions will be in the range 192.168.0.x. Since you can use a regular expression, you could do something like “grep -v 192.168.[1-5].” which would match any local address from 192.168.1.0 through 192.168.5.255.
  • Change grep ^[1-9] to match the first digit of your extensions – as shown, anything starting with a digit 1 through 9 would be considered an extension.  The idea here is that we only want to look at extensions, not trunks (which you can restrict using permit and deny statements, if necessary). Most trunks don’t begin with a number (when you do a “sip show peers” or “iax2 show peers” listing from the CLI), so this is what separates extensions from trunks.  You may have to get a bit more creative if you have a trunk that starts with a number that overlaps your extensions. If you run the entire section between the backticks (the ` characters) from a Linux command prompt,  it should show you all of your connected non-local (that is, not on your internal network) SIP or IAX extensions (depending on which line you run), but no trunks, local extensions, offline extensions, or header information.

Sharp-eyed readers may observe that I first try to delete an iptables rule before creating it.  That’s because I don’t want to create the same rule multiple times (iptables happily accepts duplicates, unfortunately).  If I get an error when trying to delete the rule, then I know that what follows will be the first attempt to create it, and I should send an e-mail and restart Asterisk when we’re all finished.  Basically, it’s supposed to be a safety mechanism to keep from repeatedly sending the same e-mail, or restarting Asterisk once a minute if for some reason the iptables rule doesn’t “take” at first.

Again, don’t forget to make the Perl script executable, and run it manually a few times to watch the output (uncomment the commented-out “print” lines during initial testing – you can remove them once you are satisfied it’s working as it should be).  After you have run it a few times, from the Linux command prompt do iptables –list and make sure everything looks okay there.

One thing you should be aware of is that when this script detects an intrusion attempt (a connection from outside the United States), after it bans the IP address it restarts Asterisk, which will interrupt any calls in progress.  That’s deliberate; I assume you want to throw the hackers off your system right now, even if it means your users may have to re-dial their calls. Howerver, if for some reason you don’t want to do that, then you can change the line `asterisk -rx “restart now”`; to `asterisk -rx “restart when convenient”`;, which will wait until there is no usage on your system to restart Asterisk.  In that case, good luck to you if the hacker just placed a call to some $100-a-minute destination! In theory IPtables will interrupt the conversation (no audio will pass) BUT that does not mean Asterisk will tear the call down right away – when an extension “just disappears”, Asterisk tends to wait a LONG time to see if it will come back, and if it never does, well that’s what we call a “zombie” call — it just won’t die (at least not until the other end disconnects)! EDIT: If you don’t want to restart the whole system but do want to throw the hacker off NOW, see the modification by “Florent” in the comments below — I have NOT tested his changes personally, but they may do what you want.

The final step is to make this execute once a minute.  I used Webmin’s “System | Scheduled Cron Jobs” to set this up:

cron job setup using WebminBut if you are more comfortable creating a cron job from the command line, by all means, feel free to do so.

Finally, I always say that suggestions for improvement are welcome, and also, if you want to translate this into some other programming language, you have my blessing. Please be sure to test it thoroughly before relying on it, because if someone manages to hack through anyway, I’m not going to pay your phone bill! Once again, the above should be considered experimental code and is not guaranteed to do anything at all.

3 thoughts on “Geolock — a Perl script for Asterisk or FreePBX users to enhance security

  1. Hi,
    I change the comportement when unwanted ip are detected :

    – Create the same iptable rule as you do
    – Hangup all call for the detected extention

    @sipchannels = `asterisk -rx "core show channels concise" | grep ^SIP/$ext | sort -n`;
    foreach (@sipchannels)
    {
    @chanline = split("!");
    $lechannel = $chanline[0];
    print "`asterisk -rx "$hangup_cmd $lechannel"`n";
    `asterisk -rx "$hangup_cmd $lechannel"`;
    }

    – unregister the extension

    `asterisk -rx "sip unregister $ext"`;

    – Never restart asterisk

    It seems to be working well !

  2. Florent: I haven’t tested this, but I assume that your code would replace the $flag = 1; in the SIP section? And that you could do something similar in the IAX2 section by replacing the references to SIP with IAX2, and of course the last part of the script that restarts Asterisk would be omitted?

    I also assume that the print statement is a diagnostic line that can be removed after initial testing (since the next line is exactly the same, except for the print “…”)?

    Anyway, this appears to be pretty clever adaptation, and I thank you for sharing it!

  3. NOTICE: All comments above this one were imported from the original Michigan Telephone Blog and may or may not be relevant to the edited article above.

Leave a Reply to michigantelephone Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Recent Posts

Recent Comments

Archives

Categories

Meta

GiottoPress by Enrique Chavez