Automatically Block Failed SIP Peer Registrations
Previously we posted a little script for quickly checking your asterisk log for failed peer registrations. Building on that script, and with the use of iptables and cron, you can easily (and automatically) block flooding traffic from your system. Iptables, a linux command line program to filter IP traffic, provides high level packet filtering before the traffic can be used to corrupt a program. Cron, the linux time scheduler, enables you to automatically run commands at scheduled time periods.
Set up IP Tables
We will not be discussing the intricacies of iptables in this post. There are excellent tutorials on iptables, and with most things linux, help is only a google away. To help identify the traffic blocked as asterisk related, a new chain will be created appropriately called… asterisk.
Here’s how to add the new chain:
iptables -N asterisk iptables -A INPUT -j asterisk iptables -A FORWARD -j asterisk
This will help identify hosts blocked for failed registrations.
Asterisk’s Log for Failed Registrations
In most cases of a sip flood attack, the host attempts registration to Asterisk. These hosts are identified in the Asterisk log (/var/log/messages) as “No matching peer found.” The following perl script scans /var/log/messages for these patterns, strips the IP address, and puts the IP address into an array.
After the file has been read, the IP addresses are counted (each count is a failed attempt), compared against the existing blocked hosts, and new occurrences are blocked. With this script we are blocking any host after the 4th failed attempt.
Here’s the script (last updated 05 SEP 2010):
#!/usr/bin/perl -w
use strict;
use warnings;
my (@failhost);
my %currblocked;
my %addblocked;
my $action;
open (MYINPUTFILE, "/var/log/asterisk/messages") or die "\n", $!, "Does log file file exist\?\n\n";
while (<MYINPUTFILE>) {
my ($line) = $_;
chomp($line);
if ($line =~ m/\' failed for \'(.*?)\' - No matching peer found/) {
push(@failhost,$1);
}
if ($line =~ m/\' failed for \'(.*?)\' – Wrong password/) {
push(@failhost,$1);
}
}
my $blockedhosts = `/sbin/iptables -n -L asterisk`;
while ($blockedhosts =~ /(.*)/g) {
my ($line2) = $1;
chomp($line2);
if ($line2 =~ m/(\d+\.\d+\.\d+\.\d+)(\s+)/) {
$currblocked{ $1 } = 'blocked';
}
}
while (my ($key, $value) = each(%currblocked)){
print $key . "\n";
}
if (@failhost) {
&count_unique(@failhost);
while (my ($ip, $count) = each(%addblocked)) {
if (exists $currblocked{ $ip }) {
print "$ip already blocked\n";
} else {
$action = `/sbin/iptables -I asterisk -s $ip -j DROP`;
print "$ip blocked. $count attempts.\n";
}
}
} else {
print "no failed registrations.\n";
}
sub count_unique {
my @array = @_;
my %count;
map { $count{$_}++ } @array;
map {($addblocked{ $_ } = ${count{$_}})} sort keys(%count);
}
Schedule the script with cron
The final step is to schedule your script to run every X minutes in cron. We’ve chosen to run our script every 2 minutes, but you can change this to 1 minute or any other time period you choose. Just remember… you can receive thousands of attempts within 2 minutes.
If you have named your script check-failed-regs.pl and placed it in your /usr/local/bin directory, your cron statement would look like this:
*/2 * * * * perl /usr/local/bin/check-failed-regs.pl &> /dev/null
Questions? Comments? We love feedback. Or, contact us for more information.
[...] Automatically Block Failed SIP Peer Registrations (Team Forrest) [...]
Amazon EC2 SIP Brute Force Attacks on Rise | VoIP Tech Chat
13 Apr 10 at 1:00 pm
Nice script. But in my case, only searching “No matching peer found” is not enough. It should also test for “Wrong password”. See my log here at the end.
Jian
======
[2010-02-15 23:27:30] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:31] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
[2010-02-15 23:27:32] NOTICE[2369] chan_sip.c: Registration from ‘”1001″ ‘ failed for ’82.90.77.205′ – Wrong password
Jian
20 Apr 10 at 11:59 pm
Hi Jian. First, We’d recommend that you not have extensions as sip usernames. That being said, you could modify the script as follows:
#!/usr/bin/perl -w use strict; use warnings; my (@failhost); my %currblocked; my %addblocked; my $action; open (MYINPUTFILE, "/var/log/asterisk/messages") or die "\n", $!, "Does log file file exist\?\n\n"; while (<MYINPUTFILE>) { my ($line) = $_; chomp($line); if ($line =~ m/\' failed for \'(.*?)\' - No matching peer found/) { push(@failhost,$1); } if ($line =~ m/\' failed for \'(.*?)\' – Wrong password/) { push(@failhost,$1); } } my $blockedhosts = `iptables -n -L asterisk`; while ($blockedhosts =~ /(.*)/g) { my ($line2) = $1; chomp($line2); if ($line2 =~ m/(\d+\.\d+\.\d+\.\d+)(\s+)/) { $currblocked{ $1 } = 'blocked'; } } if (@failhost) { &count_unique(@failhost); while (my ($ip, $count) = each(%addblocked)) { if (exists $currblocked{ $ip }) { print "$ip already blocked\n"; } else { if ($count >= 4) { $action = `iptables -I asterisk -s $ip -j DROP`; print "$ip blocked. Failed attempts: $count\n"; } else { print "$ip NOT blocked. Only $count failed attempt(s).\n"; } } } } else { print "no failed registrations.\n"; } sub count_unique { my @array = @_; my %count; map { $count{$_}++ } @array; map {($addblocked{ $_ } = ${count{$_}})} sort keys(%count); }Team Forrest
21 Apr 10 at 1:08 am
Hi,
This is a very useful script.
It works perfectly when I log in as root using SSH and run it manually. It only inserts the same rules once.
When I run it as a cron job I had to change line 39 in the last example to $action = `/sbin/iptables -I asterisk -s $ip -j DROP`; otherwise it would not insert any rules.
Now every time it runs as a cron job it keeps re-inserting the same rules.
Is there any simple way around this?
Brian.
Brian
21 Apr 10 at 10:16 am
Brian,
Thanks… you’re right and the script is updated.
Team Forrest
22 Apr 10 at 2:09 am
Hi,
Thank you, it works perfectly.
Keep up the good work.
Brian.
Brian
22 Apr 10 at 9:06 am
[...] Automatically Block Failed SIP Peer Registrations (Team Forrest) [...]
SIP Attacks From Amazon EC2 Cloud Continue | VoIP Tech Chat
16 May 10 at 10:11 pm
Thanks, glad that you pointed me to this during the VUC teleconference. Works nicely. Had to change the name of the log file from “messages” to “full”.
Very Nice…
John Ervin
25 Jun 10 at 6:48 pm
I had to change line 17 in the last example to “- Wrong password/)
Grato, Thanks
Sergio Alves
23 Jul 10 at 7:10 pm
Thanks Sergio. I’ve corrected the code.
Team Forrest
23 Jul 10 at 7:22 pm
Thanks for the great script…
heads-up that on my asterisk 1.8 installation, the IP address in the log file now includes the source port. Therefore either change line 14 to …
if ($line =~ m/\’ failed for \’(.*?):.*\’ – No matching peer found/) {
or insert 3 new lines before 14 …
if ($line =~ m/\’ failed for \’(.*?):.*\’ – No matching peer found/) {
push(@failhost,$1);
}
Thanks again
Colin
20 Aug 10 at 7:21 am
Actually, there is a vulnerability in this approach. It kinda relies on blocking the offending IP before it cracks a password. Once a password is cracked, it can be exploited from another IP address.
Therefore I suggest an upgrade thus.
1. Create a few honeypot sip extensions, say username=1, password=1, username=100, password=100
2. Point these at a honeypot context…
3. … which says
exten => _.,1,Set(ip=${CHANNEL(recvip)})
exten => _.,n,Log(WARNING,”please block ${ip}”)
exten => _.,n,Answer()
exten => _.,n,Hangup()
4. In the perl script, also grep for this alternative string
if ($line =~ m/\’please block (.*?) {
push(@failhost,$1);
}
(please double check my perl fragment as I’m not a perl programmer)
Colin
20 Aug 10 at 9:05 am
Colin,
That’s a good idea. This script does not anticipate the cracking of a password within 2 minutes and thus could be a vulnerability.
Team Forrest
20 Aug 10 at 11:54 am
Thanks a lot!! the script is very useful.
Juan
24 Aug 10 at 1:54 pm
I added some code to eliminate internal client IP addresses. My Perl is not too good BTW… Thanks for the script!
if (@failhost) {
&count_unique(@failhost);
while (my ($ip, $count) = each(%addblocked)) {
if (exists $currblocked{ $ip }) {
print “$ip already blocked\n”;
} else {
if (substr($ip,0,3) == ’10.’) {
print “$ip is in local subnet.\n”;
} else {
$action = `/sbin/iptables -I asterisk -s $ip -j DROP`;
print “$ip blocked. $count attempts.\n”;
}
}
}
} else {
print “no failed registrations.\n”;
}
Sam F
24 Aug 10 at 7:53 pm
Hi,
Thank you for the script. I have the following question.
Since I am not familiar with a perl, i would like to ask the community if the following code will work:
while () {
my ($line) = $_;
chomp($line);
if ($line =~ m/\’ failed for \’X.X.X.X.\’ – No matching peer found/) {
goto SKIP;
elsif ($line =~ m/\’ failed for \’(.*?)\’ – No matching peer found/) {
push(@failhost,$1);
}
}
SKIP:
my $blockedhosts = `/sbin/iptables -n -L asterisk`;
Where is X.X.X.X is a static IP address, that I want to except from banning. Since, I am receiving an unsuccesful registration messages from this IP the script is blocking it. But I would like to allow it to send such messages.
Is there any way to make an exception?
Thank You in advance.
Vasily869
30 Aug 10 at 6:16 pm
Instead you could use…
if (@failhost) { &count_unique(@failhost); while (my ($ip, $count) = each(%addblocked)) { if (exists $currblocked{ $ip }) { print "$ip already blocked\n"; } else { unless ($ip eq "X.X.X.X") { $action = `/sbin/iptables -I asterisk -s $ip -j DROP`; print "$ip blocked. $count attempts.\n"; } } } } else { print "no failed registrations.\n"; }Team Forrest
30 Aug 10 at 7:19 pm
Awesome script, thanks!
small issue, and I’m not sure if it’s an easy fix or not, I’m using the 2nd version that also scans for “Wrong password”, and it turns out, for whatever reason, if that’s included it actually doesn’t work for finding the
“No matching peer found”. To get around this, I’m just running 2 different scripts, but it’d be nice if I could just use the 1…
Thanks again!
Skip
30 Aug 10 at 10:08 pm
Also, I can’t figure out where to set the number of failed attempts to allow…
This is supposed to be running every minute, but if I look at my logs, I see MANY “No matching peer found” spanning over 3 to 5 minutes… I’m not sure why it’s only half working yet…
Skip
30 Aug 10 at 10:19 pm
Can you give a little more detail as to what your issues are?
Team Forrest
30 Aug 10 at 10:38 pm
@Vasily869
It might be safer to (also) whitelist your IP addresses in your iptables configuration.
Here is a fragment of my iptables …
ACCEPT all — X.X.X.X anywhere
ACCEPT all — Y.Y.Y.Y anywhere
asterisk udp — anywhere anywhere udp dpt:sip
So any traffic from my whitelisted hosts is accepted before entering the asterisk chain, and the asterisk chain is only invoked for SIP traffic.
The way the iptables instructions at the top of this article are phrased, there is a real danger of locking yourself out of your own machine.
Colin
31 Aug 10 at 10:14 am
@TeamForrest:
Thank You for the code. It works now.
@ Colin:
Thanks for your comment.
))
I alreday did it.
But without script changing, your solution doesn’t work. IPTables ban my ip even if it in a white list.
Vasily869
31 Aug 10 at 11:31 am
@TeamForrest
Wow, thanks for the response, I was waiting for an email saying there was a reply (I’m new to this comment business).
I think I may have worked out some of the issue, I’ll have to continue testing. Part of my issue was I *think* that the ‘asterisk’ chain came after everything else, I’ve just added it to the beginning by doing: iptables -I INPUT 1 -j asterisk
Mainly I’m just having a hard time verifying that it’s working… I see that the iptables rules are there, but the script is running on the same box that asterisk is, not sure how much that changes things…
Skip
2 Sep 10 at 3:58 pm
I also added this extra filter in addition to previously whitelisting my own network and domains.
# catch: [Aug 27 19:44:53] NOTICE[18826] chan_sip.c: Registration from ‘”1002″ ‘ failed for ’183.110.185.220′ – Wrong password
if ($line =~ m/ Registration from ‘”[^"]*” ]*>’ failed for ‘([^']*)’ – Wrong password/ ) {
push(@failhost,$1);
}
This might be handy too.
Simon J Mudd
5 Sep 10 at 8:58 am
Being a total newbie it would be really nice if some kind soul would add all these mods and make one script as its a bit daunting not knowing where to add all these mods to the original script.
Thanks
asteriskmad
6 Sep 10 at 12:09 pm
@Simon / @asteriskmad :
The wrong password mod was added in a comment on 21 APR, but we’ve gone ahead and updated the main code to reflect that addition (so it’s in one script).
We did not add the ip’s of what not to block… that’s more of a personal choice. In most of our deployments if we have an internal attack or a rogue system, it’s ok for that to be blocked.
Team Forrest
6 Sep 10 at 12:21 pm