http://blog.ls20.com/securing-your-asterisk-voip-server-with-iptables/



Now that you have set up your personal Asterisk® server (see Tutorial), it's time to secure it. I can't overstate the importance of this step. Without it, you could be leaving your server's VoIP ports open for anyone on the Internet, which may cost you a lot of money.

Asterisk and IPTables Logo

Click here to skip to the most important part of this article.

Here I am writing with the assumption that you already know the basics of IPTables, as well as how to make the rules persistent across reboots. There are plenty of tutorials on the Internet for these. In this article, your server's internet-facing network interface is assumed to be eth0.

This blog is hosted on DigitalOcean - SSD cloud servers from $5/mo. Sign up here and browse to "Billing" to get $10 credit for free! (referral link)

As a side note, check out my tutorial on how to get your Google Voice call history with detailed minutes usage.

Securing SSH Access

First, it is recommended to move the default SSH port (22) to a different one, in order to reduce brute-force attacks. For advanced users, also seemy other tutorial for methods to block port scanning.

How to Change the Default SSH Port

When you finish editing sshd_config, do NOT close the current SSH session! Reload ssh with service sshd reload (or service ssh reload), and open a NEW SSH connection to your new port to test. If unable to connect, immediately change the port back to 22 and reload ssh. It is possible that the new port you chose is blocked by firewall rules.

Optionally, for added security you can set up public-key authentication and disallow passwords for SSH. Follow the tutorial below, except that change the three settings as shown here:

How To Set Up SSH With Public-Key Authentication

PermitRootLogin  without-password  
PasswordAuthentication  no  
UsePAM  yes  

Note that the without-password setting above actually means that root can only login with public-key authentication, which is secure.

Setting up the IPTables Rules

Now, I present my detailed IPTables setup. Be sure to read all comments and customize these rules to your needs. After that, place them inside the corresponding file of your Linux OS, e.g. /etc/sysconfig/iptables for CentOS/RHEL, /etc/iptables.rules for Ubuntu/Debian, or /etc/iptables/rules.v4 if using iptables-persistent. Always keep a backup of your existing rules!

For your convenience, I have compiled all the IPTables rules mentioned here in this GitHub Gist (except for the optional rules). Alternatively, you can download it. These can be used on any Dedicated Server or Virtual Private Server (VPS) except for OpenVZ. If you are on that platform, seealternative rules for use on OpenVZ.

After saving the new rules, run these commands to load them:

# Reload IPTables rules  
iptables-restore < YOUR_IPTABLES_RULES_FILE  
# If you use fail2ban, also run:
service fail2ban restart  
# Make sure IPTables is enabled at system boot
chkconfig iptables on  

Do not run the first command if you use Travelin’ Man 3 (dynamic IP whitelisting for PBX in a Flash) or have other dynamic rules! In that case, please reboot your server instead.

As a side note, if your server has IPv6 enabled, you may want to also secure it with IP6Tables rules, or disable IPv6 in sysctl.conf. You can find related tutorials on the web.

Starting with an empty iptables policy, we add rules as follows.

Set default policies:

*filter  
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]

Specify additional chains:

:ICMPALL - [0:0]  
:IPSPF - [0:0]
:ASIP - [0:0]
:DPTS - [0:0]
:RLMSET - [0:0]

The INPUT chain. Replace YOUR_SSH_PORT with the port you chose:

-A INPUT -m conntrack --ctstate INVALID -j DROP  
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
# Read below for explanation
-A INPUT -m recent --update --name RLM --seconds 600 --hitcount 1 -j DROP
-A INPUT -p icmp --icmp-type 255 -j ICMPALL
# Allow DHCP traffic
-A INPUT -p udp --sport 67:68 --dport 67:68 -j ACCEPT
-A INPUT -i eth+ -j IPSPF
# Replace YOUR_SSH_PORT with your server's SSH port!
-A INPUT -p tcp --dport YOUR_SSH_PORT -j ACCEPT
-A INPUT -j ASIP
-A INPUT -j DPTS
-A INPUT -m limit --limit 10/min -j LOG
-A INPUT -j DROP

The ICMPALL chain:

-A ICMPALL -p icmp --fragment -j DROP  
-A ICMPALL -p icmp --icmp-type 0 -j ACCEPT
-A ICMPALL -p icmp --icmp-type 3 -j ACCEPT
-A ICMPALL -p icmp --icmp-type 4 -j ACCEPT
-A ICMPALL -p icmp --icmp-type 8 -j ACCEPT
-A ICMPALL -p icmp --icmp-type 11 -j ACCEPT
-A ICMPALL -p icmp -j DROP

Here, we only allow ICMP types that are commonly used and relatively safe, while blocking the others.

The IPSPF chain:

# Drop packets FROM bogon IPv4 addresses  
# Delete the line below if your server uses this range:
-A IPSPF -s 10.0.0.0/8 -j DROP
# Same as above
-A IPSPF -s 172.16.0.0/12 -j DROP
# Same as above
-A IPSPF -s 192.168.0.0/16 -j DROP
-A IPSPF -s 0.0.0.0/8 -j DROP
-A IPSPF -s 100.64.0.0/10 -j DROP
-A IPSPF -s 127.0.0.0/8 -j DROP
-A IPSPF -s 169.254.0.0/16 -j DROP
-A IPSPF -s 192.0.0.0/24 -j DROP
-A IPSPF -s 192.0.2.0/24 -j DROP
-A IPSPF -s 198.18.0.0/15 -j DROP
-A IPSPF -s 198.51.100.0/24 -j DROP
-A IPSPF -s 203.0.113.0/24 -j DROP
-A IPSPF -s 224.0.0.0/4 -j DROP
-A IPSPF -s 240.0.0.0/4 -j DROP
-A IPSPF -s 255.255.255.255 -j DROP
# Drop packets TO broadcast/multicast/loopback IPs
-A IPSPF -d 0.0.0.0/8 -j DROP
-A IPSPF -d 127.0.0.0/8 -j DROP
-A IPSPF -d 224.0.0.0/4 -j DROP
-A IPSPF -d 255.255.255.255 -j DROP
# These are some bad TCP flags used in attacks:
-A IPSPF -p tcp --tcp-flags ALL NONE -j DROP
-A IPSPF -p tcp --tcp-flags ALL ALL -j DROP
-A IPSPF -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
-A IPSPF -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP
-A IPSPF -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
-A IPSPF -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
-A IPSPF -p tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN -j DROP
# Reject NEW TCP packets w/ ACK flag. Someone could be sending packets with your server's IP as his fake IP
-A IPSPF -p tcp --tcp-flags SYN,ACK SYN,ACK -m conntrack --ctstate NEW -j REJECT --reject-with tcp-reset
# Drop NEW TCP packets w/o SYN flag
-A IPSPF -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
# Drop empty UDP packets (lengths 0 to 28)
-A IPSPF -p udp -m length --length 0:28 -j DROP
# Limit incoming NEW TCP connections to 10/sec for each IP (configurable)
-A IPSPF -p tcp --syn -m recent --update --name INSYN --seconds 1 --hitcount 11 -j DROP
-A IPSPF -p tcp --syn -m recent --set --name INSYN -j RETURN
-A IPSPF -j RETURN

With the above rules, we block traffic from IPv4 bogon addresses and drop tcp packets with various bad flags. We also reject new TCP ACKs without SYN, new packets without SYN as well as empty udp packets, and limit incoming new TCP from each host to 10/sec (configurable).

The DPTS chain:

# Change to ACCEPT if FTP server:  
-A DPTS -p tcp --dport 21 -j DROP
# Remember to change your SSH port first!
# If you use port 22, change this to ACCEPT!
-A DPTS -p tcp --dport 22 -j RLMSET
-A DPTS -p tcp --dport 23 -j RLMSET
# Change to ACCEPT if MAIL server:
-A DPTS -p tcp --dport 25 -j RLMSET 
# Note: Port 80 and/or 443 are needed to access the FreePBX GUI.
# For security, do NOT open them here. Use SSH port forwarding instead.
-A DPTS -p tcp --dport 80 -j DROP
-A DPTS -p tcp --dport 443 -j DROP
-A DPTS -p tcp --dport 1433 -j RLMSET
-A DPTS -p tcp --dport 3128 -j RLMSET
# Change to ACCEPT if Internet-facing MySQL server:
-A DPTS -p tcp --dport 3306 -j RLMSET 
-A DPTS -p tcp --dport 3389 -j RLMSET
-A DPTS -p tcp --dport 4899 -j RLMSET
-A DPTS -p tcp --dport 5900 -j RLMSET
-A DPTS -j RETURN

Here, we block the "bad guys" that scan any of these ports from reaching ALL ports for 10 minutes (configurable). This works in conjunction with Rule #4 in the INPUT chain.

The RLMSET chain:

-A RLMSET -m recent --set --name RLM -j DROP  

Read below for details of the ASIP chain.

This section of the IPTables rules file should end with:

COMMIT  

Securing Asterisk with IPTables

Here comes the most important part, in which I will discuss how we can block the "bad guys" from our Asterisk server, while allowing access by legitimate users. Before we begin, make sure that you have followed best practices such as setting strong passwords for root/maint and all Asterisk extensions; keeping CentOS, Asterisk and FreePBX modules up-to-date; and requiring a dial-out PIN for certain routes. The approach here is suitable for use on Asterisk servers with the SIP protocol.

Step 1: Apply for a DNS hostname from a dynamic IP service provider. For example, FreeDNS or No-IP.com. Let's assume that you have set up the hostname YOUR_HOSTNAME.no-ip.com
(Alternatively, if you own any domain name, you can also set up a new sub-domain for this purpose. Make sure it's hard to guess.)

Step 2: After setting up your Asterisk server, inform all your users to use this dynamic IP hostname YOUR_HOSTNAME.no-ip.com as the server name. Do NOT let them use the server's IP address directly. 
(Note: If you cannot do this due to a large user base, try relaxing the IPTables rules, at the cost of less protection.)

Step 3: Based on the IPTables rules above, continue to add the following:

The ASIP chain, put before COMMIT in the previous section's rules:

-A ASIP -p tcp --dport 5060:5082 -j ACCEPT  
-A ASIP -p udp --dport 5060:5082 -m recent --update --name MYSIP -j ACCEPT
-A ASIP -p udp --dport 5060:5082 -j DROP
-A ASIP -p udp --dport 10000:20000 -j ACCEPT
-A ASIP -j RETURN

Add the following rule on TOP of the INPUT chain (as the first rule). Read below for explanations.

-A INPUT -p tcp --dport 5060:5082 -m conntrack --ctstate RELATED,ESTABLISHED -m recent ! --rcheck --name MYSIP -j DROP  

We will also place some rules into the raw table (credit). Replace the two YOUR_HOSTNAME.no-ip.com with your actual hostname.

*raw  
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:BADSIP - [0:0]
:TCPSIP - [0:0]
:UDPSIP - [0:0]
:NEWSIP - [0:0]
# IMPORTANT: Replace "YOUR_HOSTNAME.no-ip.com" with the dynamic IP hostname you have set up!
-A PREROUTING -i eth+ -m recent --update --name MYSIP -j ACCEPT
-A PREROUTING -i eth+ -p tcp --dport 5060:5082 -m string --string "sip:YOUR_HOSTNAME.no-ip.com" --algo bm --icase -j NEWSIP
-A PREROUTING -i eth+ -p udp --dport 5060:5082 -m string --string "sip:YOUR_HOSTNAME.no-ip.com" --algo bm --to 1500 --icase -j NEWSIP
-A PREROUTING -i eth+ -m recent --update --name BADSIP -j DROP
-A PREROUTING -i eth+ -p tcp --dport 5060:5082 -j TCPSIP
-A PREROUTING -i eth+ -p udp --dport 5060:5082 -j UDPSIP
-A TCPSIP -m string --string "sundayddr" --algo bm -j BADSIP
-A TCPSIP -m string --string "sipsak" --algo bm -j BADSIP
-A TCPSIP -m string --string "sipvicious" --algo bm --icase -j BADSIP
-A TCPSIP -m string --string "friendly-scanner" --algo bm -j BADSIP
-A TCPSIP -m string --string "iWar" --algo bm -j BADSIP
-A TCPSIP -m string --string "sip-scan" --algo bm -j BADSIP
-A TCPSIP -m string --string "sipcli" --algo bm -j BADSIP
-A TCPSIP -m string --string "eyeBeam" --algo bm -j BADSIP
-A TCPSIP -m string --string "VaxSIPUserAgent" --algo bm -j BADSIP
-A TCPSIP -m string --string "sip:nm@nm" --algo bm -j BADSIP
-A TCPSIP -m string --string "sip:carol@chicago.com" --algo bm -j BADSIP
-A UDPSIP -m string --string "sundayddr" --algo bm --to 1500 -j BADSIP
-A UDPSIP -m string --string "sipsak" --algo bm --to 1500 -j BADSIP
-A UDPSIP -m string --string "sipvicious" --algo bm --icase --to 1500 -j BADSIP
-A UDPSIP -m string --string "friendly-scanner" --algo bm --to 1500 -j BADSIP
-A UDPSIP -m string --string "iWar" --algo bm --to 1500 -j BADSIP
-A UDPSIP -m string --string "sip-scan" --algo bm --to 1500 -j BADSIP
-A UDPSIP -m string --string "sipcli" --algo bm --to 1500 -j BADSIP
-A UDPSIP -m string --string "eyeBeam" --algo bm --to 1500 -j BADSIP
-A UDPSIP -m string --string "VaxSIPUserAgent" --algo bm --to 1500 -j BADSIP
-A UDPSIP -m string --string "sip:nm@nm" --algo bm --to 1500 -j BADSIP
-A UDPSIP -m string --string "sip:carol@chicago.com" --algo bm --to 1500 -j BADSIP
-A BADSIP -m recent --set --name BADSIP -j DROP
-A NEWSIP -m recent --set --name MYSIP -j ACCEPT
COMMIT  

Optionally, add the following rules before the COMMIT line above to help defend against SIP flooding and/or brute force attacks by rate-limiting SIP requests (credit). The parameters are customizable.

-A TCPSIP -m string --string "REGISTER sip:" --algo bm -m recent --set --name SIP_R  
-A TCPSIP -m string --string "REGISTER sip:" --algo bm -m recent --update --seconds 10 --hitcount 20 --rttl --name SIP_R -j DROP
-A UDPSIP -m string --string "REGISTER sip:" --algo bm --to 1500 -m recent --set --name SIP_R
-A UDPSIP -m string --string "REGISTER sip:" --algo bm --to 1500 -m recent --update --seconds 10 --hitcount 20 --rttl --name SIP_R -j DROP
-A TCPSIP -m string --string "INVITE sip:" --algo bm -m recent --set --name SIP_I
-A TCPSIP -m string --string "INVITE sip:" --algo bm -m recent --update --seconds 5 --hitcount 20 --rttl --name SIP_I -j DROP
-A UDPSIP -m string --string "INVITE sip:" --algo bm --to 1500 -m recent --set --name SIP_I
-A UDPSIP -m string --string "INVITE sip:" --algo bm --to 1500 -m recent --update --seconds 5 --hitcount 20 --rttl --name SIP_I -j DROP

Again, for your convenience, I have compiled all the IPTables rules mentioned here in this GitHub Gist (except for the optional rules). Alternatively, you can download it. Be sure to read all comments and customize these rules to your needs.

Explanation

The IPTables string module is used to identify legitimate users while filtering out the "bad guys". The first three lines in PREROUTING will look for the string sip:YOUR_HOSTNAME.no-ip.com in the incoming packets.

When legitimate users try to register using SIP clients, as long as their server addresses are correctly set to your chosen hostname, the requests should contain this string. By using the recent module, once a legitimate user has been identified, his or her IP address is added to the list MYSIPand future connections will be accepted instantly.

The other lines in PREROUTING try to recognize strings in common SIP scanners and permanently block them (well, until the server is rebooted. I plan to cover the saving and restoring of recent lists in a future article) by adding their IPs to BADSIP. My experiments using VoIP honey pots showed that over 90% of scan attempts were identified with these rules. In particular, the one with sipvicious received the most hits.

Reason for different treatment for TCP connections

Did you notice that we added an extra rule to the INPUT chain and also accepted incoming TCP port 5060 connections by default in ASIP? There is a twist here. It turns out that for TCP connections, if we do not accept the initial TCP handshake, IPTables will NOT be able to see the SIP register packets, which means the string module will not work.

After spending much time trying to solve this problem, I finally came up with an idea. How about we accept all TCP SIP connections initially, but block any further packets from that established connection until we can use the string module to identify whether it is from a legitimate user? I tried it, and Voila! It works!

Further Readings

This section provides some additional tips that may be useful.

Strengthen security with Fail2Ban

You can further strengthen your server's security by using Fail2Ban to monitor logfiles and automatically ban IPs with repeated failed attempts. Install Fail2Ban using yum, then add the line service fail2ban restartbefore exit 0 in your /etc/rc.local. After that, create or edit the file /etc/fail2ban/jail.local. See example. Change the SSH and/or Webmin ports if needed. When finished, make the file immutable with chattr +i, to avoid it being overwritten by the sysadmin module at FreePBX reload.

Limiting access to trusted IP addresses

If you run your Asterisk server internally, e.g. in a company office setting where all users have fixed IP addresses or are within a certain IP range, it is recommended to use IPTables rules to limit access to your server from those trusted IPs and/or subnets only (as small as possible). Do not open ANY port to the Internet unless you absolutely need to.

For more protection, find the permit option for each of your Asterisk extensions, and replace 0.0.0.0/0.0.0.0 with your trusted IP range. You may also want to consider setting a low "concurrent calls limit" for each extension, as well as for each trunk (credit). To block anonymous callers, turn OFF the "Allow SIP Guests" option in FreePBX SIP settings.

Alternatively, if you have a few roaming users, check out Travelin’ Man 3(for PBX in a Flash) which cleverly integrates access control with dynamic IP updates. By the way, I achieved the same goal using a different method. If interested, refer to my Disqus comments below.

Raising the recent module's limits

Optionally, you can modify some parameters of the recent module. By default, each "recent" list will hold 100 IPs, while at most 20 packets from each IP will be remembered. In other words, older IPs will be removed once a list reaches 100 IPs, and we cannot set the --hitcount parameter to values higher than 20. To remove this restriction, create a file at /etc/modprobe.d/xt_recent.conf with this line and reboot your server.

options xt_recent ip_pkt_list_tot=100 ip_list_tot=2000  

To learn how to manually manage the recent lists mentioned in this article such as MYSIP or BADSIP, browse to this IPTables manual and search for "xt_recent".

Disable unneeded Asterisk modules

By default, Asterisk listens on many TCP and UDP ports as can be shown by netstat -anput | grep asterisk. If you are like me and only use SIP but not IAX2, and has no VoIP hardware cards, you can disable some Asterisk modules and close those ports. This will further increase security in case your firewall goes down. For your reference only, here are the modules I disabled in /etc/asterisk/modules.conf.

; Don't load skinny (tcp port 2000)  
noload => chan_skinny.so  
; Don't load MGCP (udp port 2727)
noload => chan_mgcp.so  
; Don't load dundi (udp port 4520)
noload => pbx_dundi.so  
; Don't load unistim (udp port 5000)
noload => chan_unistim.so  
; Don't load ooh323 (tcp port 1720)
noload => chan_ooh323.so  
; Don't load IAX2 (udp port 4569)
noload => chan_iax2.so  
; Don't load SQLite because of crashes with heavy call volumes
; SQLite (version 2) is NOT needed for Asterisk
noload => cdr_sqlite.so  
noload => res_config_sqlite.so  

If you don't use the Flash Operator Panel (FOP), it is a good idea to disable it. This can be achieved using these two lines in /etc/amportal.conf. For FreePBX 2.10 and above, uninstall the FOP module from Module Admin. There was a related FreePBX bug which has since been fixed.

FOPRUN=FALSE  
FOPDISABLE=TRUE  

In addition, adding the line below to your /etc/asterisk/manager.conf in the [general] section will let Asterisk only listen for control connections from localhost (tcp port 5038). Alternatively, you can restrict IP access via the deny and permit options.

bindaddr = 127.0.0.1  

Possibility on relaxing the IPTables rules

Note that some of the IPTables rules depend on all your users setting the correct hostname in their VoIP clients. The "bad guys" won't easily know it so the scheme is secure. If you have a large user base or consider these overly restrictive, however, here are two alternative methods.

Method 1: Browse to my IPTables ruleset. From the raw table, remove all lines with NEWSIP (126~130, 157) while keeping the others. Then append ONLY that raw table to the end of your existing IPTables rules. Because this table only protects the SIP ports, you must properly secure or close all other ports in your existing rules.

Method 2: Follow the link in Method 1 and copy into your favorite editor. From there, change the -j DROP on line 116 to -j ACCEPT, and remove these lines: 29, 115, 126~130 and 157. Be sure to read all comments and customize other rules to your needs. Finally, use those to replace your existing IPTables rules (keep a backup of the original file).

By using one of the methods above, your server should be able to block some common SIP scanners, but is less protected than using the complete ruleset. To improve security, I suggest that you also drop traffic from these IPs and subnets (updated periodically), where SIP scanners with randomized strings are used that are difficult to block.

Another option to relax the rules is to filter by User-Agent of VoIP clients instead of by your chosen hostname. Assuming that all of your users useZoiper (versions for: PC Mac LinuxAndroidiPhone), you can replace lines 129~130 in the raw table with:

-A PREROUTING -i eth+ -p tcp --dport 5060:5082 -m string --string "Zoiper" --algo bm --icase -j NEWSIP  
-A PREROUTING -i eth+ -p udp --dport 5060:5082 -m string --string "Zoiper" --algo bm --to 1500 --icase -j NEWSIP

How effective are these new rules?

After running your server with the new rules for a while, you can easily check how effective the rules are with this command:

iptables -nvL -t raw | grep BADSIP  

Look at the lines with DROP or BADSIP. Add together all those numbers. The result is how many scan attempts from the "bad guys" had been thwarted. Your VoIP server is now better protected! Next, buy me a beer. Just kidding! You can instead share this blog with your friends.

I hope you enjoyed reading this article and that it can help you further secure your Asterisk server against online attacks.

Please share this post if you like it, and do not hesitate to write your comments or questions in the Disqus form below.

Next Article: Scripts for Auto IP Updates on Amazon EC2 or DigitalOcean 
Previous Article: Setting Up Your Personal Asterisk VoIP Server

Share this post:

Return to Lin's Tech Blog Homepage

Disclaimer: All content provided on this blog is for informational purposes only. The owner of this blog makes no representations as to the accuracy or completeness of any information on this site or found by following any link on this site. All trademarks mentioned herein belong to their respective owners.
    The owner of this blog will not be liable for any errors or omissions in this information nor for the availability of it. The owner will not be liable for any losses, injuries, or damages from the display or use of this information.