now | writings | rss | github | twitter | contact

Properly stopping a SIP flood

posted to writings on apr 11th, 2010 with tags asterisk, nerd, openbsd, ruby, security, superblock, voip, and work and commented on 38 times

At about 9am yesterday morning, I noticed on the monitor that the CPU utilization of one of my servers was abnormally high, in addition to a sustained 1mbit/sec of inbound traffic and 2mbits/sec of outbound traffic. syslog messages from Asterisk showed it to be a SIP brute force attack, so I dropped the offending IP (an Amazon EC2 instance IP) into /etc/idiots to block it and went back to my work.

A while later, I noticed the traffic still hadn't died down, so I reported the incident to Amazon and my server's network provider. No luck on either front; Amazon just sent back a form reply stating the incident was forwarded to the EC2 instance's owner (yeah, seriously) and the network provider said they wouldn't bother adding an ACL to their border equipment unless it was needed to protect their entire network. With the IP blocked on my server, the CPU utilization had died down and it was no longer sending out reply traffic, but I was worried about the inbound garbage traffic counting towards the server's monthly bandwidth cap.

This morning, 24 hours after the flood started, there was still no progress from Amazon (even after a second report filed) nor the network provider on stopping it. Usually these SIP attacks try sequential SIP account numbers and common usernames, find valid accounts, then try to brute force each one's password with common ones, then move on. Presumably the valid account/password combinations are later used for spamming or other fraudulent purposes. For whatever reason, this particular scanner didn't "move on" and had been trying the same account hundreds of thousands of times over many hours.

I thought about reconfiguring Asterisk to use an empty password for the SIP account being brute forced so the scanning script would think it found a match and stop trying, but I wasn't sure if it would consider a REGISTER request with any password valid on an account with no password. Since the account in question was also in use at the time, I didn't want to disrupt it by changing its password, and taking down Asterisk just to have something custom respond to the attacker's SIP traffic was out of the question.

I took the IP out of the /etc/idiots file and reconfigured pf to redirect the traffic to a different local port where I could have something else listening:

pass in quick proto udp from to any port 5060 rdr-to port 5061

With the traffic from the attacking IP now going to port 5061 instead of 5060, I cooked up a simple ruby script to bind to 5061 and show me the incoming SIP messages:

require "socket"

s =
s.bind(nil, 5061)
while true
  packet = s.recvfrom(1024)
  puts packet.inspect

Instantly dozens of messages scrolled by within seconds of starting it:

REGISTER sip:[...] SIP/2.0
Via: SIP/2.0/UDP;branch=z9hG4bK-2528031440;rport
Content-Length: 0
From: "2011" <sip:2011@[...]>
Accept: application/sdp
User-Agent: friendly-scanner
To: "2011" <sip:2011@[...]>
Contact: sip:123@
Call-ID: 3361196543
Max-Forwards: 70

["AF_INET", 5182, "", ""]

REGISTER sip:[...] SIP/2.0
Via: SIP/2.0/UDP;branch=z9hG4bK-1678605574;rport
Content-Length: 0
From: "2011" <sip:2011@[...]>
Accept: application/sdp
User-Agent: friendly-scanner
To: "2011" <sip:2011@[...]>
Contact: sip:123@
Call-ID: 1992838843
Max-Forwards: 70

["AF_INET", 5209, "", ""]

Oh, the irony of the attacking script using a user-agent string of "friendly-scanner"...

With a little parsing added to the script, it could now send back a valid "OK" message with the specific Via, From, To, Call-ID, and CSeq lines from the request. Hopefully these would be enough for the attacking script to think it got a valid reply for one of its requests and finally move on.

require "socket"

s =
s.bind(nil, 5061)
while true
  packet = s.recvfrom(1024)

  via = packet[0].match(/Via: (.+);rport/)[1]
  from = packet[0].match(/From: (.+)/)[1]
  to = packet[0].match(/To: (.+)/)[1]
  call_id = packet[0].match(/Call-ID: (.+)/)[1]
  cseq = packet[0].match(/CSeq: (\d+) REGISTER/)[1]

  remote_ip = packet[1][3]
  remote_port = packet[1][1].to_i

  puts packet.inspect

  if packet[0].match(/^REGISTER /)
    ret = "SIP/2.0 200 OK\r\n" +
      "Via: #{via};received=#{remote_ip}\r\n" +
      "From: #{from}\r\n" +
      "To: #{to}\r\n" +
      "Call-ID: #{call_id}\r\n" +
      "CSeq: #{cseq.to_i + 1} REGISTER\r\n" +

    puts "sending to #{remote_ip}:#{remote_port}:\n#{ret}"

    s.send(ret, 0, remote_ip, remote_port)

I ran this new version of the script and, after about 25 messages scrolled by in a second or two, everything stopped. The SIP traffic stopped coming in, the server's bandwidth utilization returned back to nil, I put the attacking IP back in /etc/idiots, and everything was back to normal.

Comments? Contact me via Twitter or e-mail.


Karl (authentic) on april 11th, 2010 at 23:05:53:


i'd just like you and your readers to know that we have had 2 phone systems hit today by exactly the same sort of traffic. Both from different Amazon EC2 IP addresses, and both completely maxing out the CPU and bandwidth of the phone systems. (one system is in Singapore, and one in the UK). We have reported both instances to Amazon's abuse team, lets see if they decide to do anything about it! :)

Fred Posner (authentic) on april 12th, 2010 at 00:35:27:

We also received an attack from EC2 this weekend and Amazon's response came 48 hours later... in which they asked us to respond with the same information we had already given them in the initial report. I really like your idea... I've done more of a reactive IP block but a redirect is fantastic.

MCR (authentic) on april 12th, 2010 at 02:33:25:

Can you explain a bit more about /etc/idiots pls?

I have /etc/bastards in a persist table and I updated it with the "friendly scanner" IP address also but just doing this and replacing the table did not do the trick for blocking the traffic.

Is there a script you run for /etc/idiots or do you just replace the table via pfctl?

joshua (authentic) on april 12th, 2010 at 04:24:26:

/etc/idiots is just the file i use on all of my servers with the corresponding rules:

table <idiots> persist file "/etc/idiots"
block in quick log from <idiots>

and then i just run "pfctl -f /etc/pf.conf" whenever /etc/idiots is changed.

MCR (authentic) on april 12th, 2010 at 08:17:36:

Thanks. This is what I had but it did not work so well for me.

I was getting hit pretty hard via an openbsd firewall which was protecting the sip boxes behind it.

When I
1)added the Amazon EC2 subnet to /etc/bastards and reloaded the table
2) killed the corresponding states

I could still see the traffic coming in and was perplexed for a while.

My problem was that I allowed outgoing by default and the internal servers were so backed up on sip requests their responses were creating more state so it meant my new rules did not even get checked when the next set of incoming packets packets passed :-(

So I had to block on $internal_if from any to $Bastards.IP.address as well. Then flush the states for those outgoing packets before pf checked the tables to decide what to do with those packets.

I will be running your script as well next time too - thanks :-)

Tim Panton (authentic) on april 12th, 2010 at 02:41:39:

Nice defence. I wonder if it wouldn't be even more fun to hire an ec2 instance to send a flood of replies to it with faked udp from addresses. Does EC2 -> EC2 bandwidth cost ?

Shlomo Swidler (authentic) on april 12th, 2010 at 06:25:42:

You can't spoof packets within EC2 - the virtualized network stack won't allow it. And attacking a server without the owner's permission is a violation of the EC2 Terms of Service.

But you do ask an interesting question: How can you communicate to another EC2 account without incurring bandwidth costs?

EC2-to-EC2 bandwidth is free within the same "availability zone".

EC2 has (today) three regions: us-east-1 (N. Virginia), us-west-1 (N. California), and eu-west-1 (Ireland). Each region has a number of availability zones within. For example, there is us-east-1a, us-east-1b, us-east-1c, and us-east-1d.

Transfers within the same availability zone are free. Transfers across availability zones but in the same region cost a little. Transfers out of the region, to anywhere (even including into a another region) cost more.

If you wanted you could discover what availability zone the target was running in. It's complicated by the fact that the availability zones are named differently for each account. Eric Hammond has a technique to discover the correspondence of availability zone names across accounts.

Three different accounts that are performing no other communications to other EC2 instances could collaborate to discover the target's availability zone as follows:
1) Use Eric Hammond's technique to discover the correspondence of your availability zones across the three accounts.
2) Each account should choose a different physical availability zone (indicated by the Offering ID in his technique) and use a different zone for the following steps:
3) Each account launches an instance in a different zone and sends some packets over to the target - perhaps a ping or a SIP negotiation. Then, terminate the instances.
4) Wait a few hours until those hourly networking charges show up in your Usage Report (which you can get via the AWS Account management portal).

Some accounts will show a "Regional" bandwidth charges, and one may not. The account that doesn't show any Regional charges is the one in which the zone used matches the target's availability zone (because that's the one where the availability zone actually matches that of the target). If all of the accounts show "Regional" charges then the remaining, untested zone corresponds to the targets availability zone.

The availability zone of the target would need to be communicated not via its name (us-east-1a) but via its Reserved Instance Offering ID. Then, any other account could look at its own Reserved Instance Offering IDs and discover which availability zone of that account matches.

bob (authentic) on april 12th, 2010 at 02:48:14:

nice job, chief.

Fred Posner (authentic) on april 12th, 2010 at 04:45:49:

I'm still trying to find out why amazon refuses to respond. I'm about ready to start auto dialing their toll-free numbers until the attacks stop.

Tom Stordy-Allison (authentic) on april 12th, 2010 at 04:48:34:

We've also been attacked from an Amazon EC2 - for about 40 hours it has been at us.

Gonna give that script a go and fingers crossed we will get rid of it!

Tom Stordy-Allison (not authenticated) on april 12th, 2010 at 05:34:21:

Worked a dream.

For anyone on linux use:

iptables -t nat -A PREROUTING -i eth0 -source -p udp --dport 5060 -j REDIRECT --to-port 5061

I had to set the bind ip address also to receive packets:

s.bind("", 5061)


aquarat (authentic) on november 3rd, 2010 at 04:24:58:

Thanks! Although even thought the app/script is working the flood hasn't stopped... so it's possible the attackers have become wise to this.

David (not authenticated) on april 12th, 2010 at 06:23:24:

Two words for you:


Works like a charm. Agreed with Tom Stordy, in Linux you must bind to other address.


Leif Madsen (authentic) on april 12th, 2010 at 12:23:06:

Damn, this is one heck of a clever way of stopping the attack. Thanks for this!

KC (not authenticated) on april 12th, 2010 at 14:29:36:

If possible, use IPTABLES to block ALL ip addresses except for IP's that manage the PBX. That way, an attacker's kiddie script wont even pick up on your system and it will 'move on' before it ever begins its brute-force attack.

joshua (authentic) on april 12th, 2010 at 14:39:02:

That's nice in theory, but in reality many PBXes have SIP clients connecting from dynamic IPs on cable modems and other networks where locking down IP access isn't feasible.

Cheap calls (authentic) on april 12th, 2010 at 20:34:24:

This Amazon friendly-scanner generated about 15GB traffic this weekend! Same ammount couple weeks back.

Very nice trick, I did this (linux)
iptables -I INPUT -s -j DROP
But that generated in 2 GB blocked data in a couple of hours.
iptables -I INPUT -s -j REJECT
Was more effective, the scanning stopped within minutes (4MB traffic).

I also reported this to EC2 abuse.

brodie (not authenticated) on april 21st, 2010 at 06:37:14:

The <User-Agent: friendly-scanner> showed up from IP today. Attempting to register.

Reported to

whardier (not authenticated) on may 14th, 2010 at 06:10:06:

I had to bind to and I verified that I am sending back a 200 OK

Unfortunately. I believe there is no return path once my packet hits the remote server. It's a school and their firewall allows this traffic outbound but I believe all inbound is blocked due to the lack of response I get while running this script. :( I was hoping this would work - I had thought about writing this in python on my way in to work this morning and conveniently found your post before I started in.

sandro gauci (authentic) on june 22nd, 2010 at 04:12:16:

added a tool to SIPVicious that may help in mitigation: svcrash.

Blog post about it:

Also posted an FAQ:

sthen (not authenticated) on march 12th, 2011 at 01:58:51:

I'm finding jcs' script considerably more effective than svcrash...

Mike (authentic) on november 28th, 2010 at 16:41:45:

this doesnt seem to work, maybe my redirect is out of wack but using the linux redirect mentioned above should work... anything i can try?

p.s When i run the script it just stays blank:

tmp]# ./monitorhack.rb


Atlantic (authentic) on february 21st, 2011 at 04:09:44:

The empty password route is very easy with friendly-scanner. Attempts will stop immediately. I usually set up a special context for the hacker, so I can talk to them. It's hilarious, you should try it.

Iraklis Mathiopoulos (authentic) on february 25th, 2011 at 23:13:57:


This is true hacking. Worked like a charm. I had constant 4MBps ingress traffic from 4 compromised boxes, and they stopped immediately. I had contacted both and my ISP but they didnt bother to do anything.

Once again , thanks :)

al (not authenticated) on february 28th, 2011 at 09:24:22:

13 call_id = packet[0].match(/Call-ID: (.+)/)[1]
14 cseq = packet[0].match(/CSeq: (\d+) REGISTER/)[1]

test.rb:14: undefined method `[]' for nil:NilClass (NoMethodError)

joshua (authentic) on february 28th, 2011 at 09:43:49:

then you are probably dealing with different SIP packets that don't match that pattern. you can do something like

if !packet[0].match(/CSeq: (\d+) REGISTER/)
  puts packet.inspect

and look at what they're actually sending.

Gustavo (authentic) on june 8th, 2011 at 02:24:47:

I just wanted to thank you for your article. We were been hit by this friendly-scanner for more than 2 days. We were able to block the ofending IP on the firewall, but it continued consuming 600kbps of our link.
This solution worked perfectly!

MH (not authenticated) on june 10th, 2011 at 09:23:16:

thank you very much for this info,

We had this brute forcing our server all day long until I redirected all the traffic to another machine and ran nc -lu 5060 and pasted the correct reply back.

all stopped now and offending ip banned.

Henk de Greef (authentic) on september 23rd, 2011 at 02:04:01:


Nothing worked to get rid of this Chinese "friendly-scanner". It really overwhelmed my entire system.

After loading youre script and mangling the firewall, within 5 seconds the flood stopped.
Many thankyous !!!

Phill (not authenticated) on october 15th, 2011 at 18:00:11:

Been bothering me for days and this stopped the attack within minutes.
Thank you.

Chris Ruehl (not authenticated) on october 30th, 2011 at 23:03:04:

Your solution is good, but the guys in china already changed the name for the Agent and drives me nuts. We are in Hong Kong and had currently an issue with the Somalia VoIP mafia. Costs my company a 2k US. We figure out an other solution using limits with Iptables.
independent from the agent string.
We provide the solution on
(More security)


Mike (not authenticated) on march 7th, 2012 at 01:31:31:

Hi Chris,

You're right, it doesn't work when name is changed

Where is your solution?

is it this one?

iptables -I INPUT -p udp -m udp --dport 5060 -m string --string "REGISTER sip:" --algo bm -m recent --set --name VOIP --rsource
iptables -I INPUT -p udp -m udp --dport 5060 -m string --string "REGISTER sip:" --algo bm -m recent --update --seconds 60 --hitcount 12 --rttl --name VOIP --rsource -j DROP
iptables -I INPUT -p udp -m udp --dport 5060 -m string --string "INVITE sip:" --algo bm -m recent --set --name VOIPINV --rsource
iptables -I INPUT -p udp -m udp --dport 5060 -m string --string "INVITE sip:" --algo bm -m recent --update --seconds 60 --hitcount 12 --rttl --name VOIPINV --rsource -j DROP
iptables -I INPUT -p udp -m hashlimit --hashlimit 6/sec --hashlimit-mode srcip,dstport --hashlimit-name tunnel_limit -m udp --dport 5060 -j ACCEPT
iptables -I INPUT -p udp -m udp --dport 5060 -j DROP

ZiX (not authenticated) on march 17th, 2012 at 22:26:56:

I've got same attack that was started more than week ago. I found your script today and try it. Attack has been stopped in 5 seconds.

Big thanks,
Alexey Pletnev

Greg (authentic) on april 9th, 2012 at 19:35:51:

I was able to use your script to stop an attack on my office VOIP line. Thanks a million!

aL (not authenticated) on june 6th, 2012 at 01:52:00:

man, this worked like a charm...

thank you!!

M (authentic) on july 12th, 2012 at 14:34:27:

Thanks for sharing Joshua, nice solution!

This solved it for me as well today, stopping a persistent flood that kept going even after dropping packets from the scanners source IP.

クロ (authentic) on september 10th, 2012 at 06:23:14:


Got flooded the last three days (i need to lock my SIP port down better) and lost about 50 gigs of bandwidth which i have to pay for with no sign of it stopping.

Your script just saved me a lot of frustration and probably a lot of money! So my thanks do not feel like enough.

Thank You! :)
(got a paypal? i'll send you some beer money)

Russell McConnachie (authentic) on september 15th, 2012 at 03:58:33:

Thanks for this, I've been flooded for the last 10 hours consuming almost 500G of traffic. In my kamailio/sip-router configuration I've added the following, instead of launching a ruby script to perform the reply:

if ($ua == "friendly-scanner" || $ua =~ "scanner") {
xlog("L_INFO", "Responding to friendly-scanner attack with 200 OK to make them stop");
sl_send_reply("200", "OK");

It has worked great, the first 200 OK response caused the friendly-scanner to stop.