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. If not, refer to tutorials on the web. In this article, your server's internet-facing network interface is assumed to be eth0.

As a side note, check out my tutorial on setting up your own IPsec VPN server with both IPsec/L2TP and Cisco IPsec.

Securing SSH Access

First, it is recommended to change the default SSH port (22) to a different one, to reduce brute-force attacks. Advanced users, also see my 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 SSH key pairs and disallow passwords. Follow the tutorial below, but edit the 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 using SSH keys, which is secure.

Setting up the IPTables Rules

Now, I present my detailed IPTables setup. Be sure to read all comments and customize them to your needs. Then use them to replace the IPTables rules file, 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 existing rules!

For your convenience, I have compiled all the IPTables rules in the linked GitHub Gist (except for the optional rules). They can be used on any dedicated server or virtual private server (VPS), with the exception of OpenVZ VPS.

Link to the IPTables rules:
https://gist.github.com/hwdsl2/7e295eaa6c7919508b03

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! Please reboot your server instead.

If your server has IPv6 enabled, you may want to also secure it with IP6Tables, or disable IPv6 in sysctl.conf. Search for 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 the rate of new TCP connections from each host to 10/second (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 added 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 is 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 start, 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 updated, 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, create a new sub-domain under any domain name you own. Choose a sub-domain that is hard to guess.)

Step 2: After setting up your Asterisk server, inform all your users to use your chosen hostname (e.g. 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 in the linked GitHub Gist (except for the optional rules). Make sure that you read all comments and customize them to your needs.

Link to the IPTables rules:
https://gist.github.com/hwdsl2/7e295eaa6c7919508b03

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 connect 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 is identified, his or her IP address is added to the list MYSIP, and future connections are accepted instantly.

The other lines in PREROUTING try to recognize 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 honeypots showed that over 90% of scan attempts were identified with these rules. In particular, the rule 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, while accepting incoming TCP port 5060 connections by default in ASIP? There is a twist here. It turns out that if we do not accept the initial TCP handshake, IPTables won't receive the SIP register packets, and the string module will not work.

After spending much time debugging this issue, I finally came up with an idea. What if we accept all TCP SIP connections initially, but block further packets from that established connection until the string module could identify whether it is from a legitimate user? I tried it, and Voila! It worked!

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 log files and automatically ban IPs with repeated failed attempts. Install it using yum, then create or edit /etc/fail2ban/jail.local. Here is an example. Modify the SSH and/or Webmin ports as needed. When finished, make the file immutable with chattr +i, to prevent 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). Better yet, do not open any port to the Internet unless you absolutely need to.

For more protection, find the permit option for 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 and 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 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 raise some limits of the recent module. By default, each list will hold 100 IPs, and no more than 20 packets from each IP are tracked. In other words, older IPs are removed once a list reaches 100 IPs, and we cannot set the --hitcount parameter to values > 20. To raise the limits, create /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 above 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 only use SIP but not IAX2, and have no VoIP hardware cards, you can disable some Asterisk modules and close those ports. This could increase security in case your firewall goes down. For your reference, 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, you must uninstall the FOP module from Module Admin.

FOPRUN=FALSE  
FOPDISABLE=TRUE  

Furthermore, 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 could restrict IP access via the options deny and permit.

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.

Option 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.

Option 2: Follow the link in "Option 1" above 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.

Another option to relax the rules is to filter by User-Agent of VoIP clients. Assuming that all of your users use Zoiper (versions for: PC/Mac, Android, iPhone), 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

Return to Lin's Tech Blog Homepage



View or Post


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.

Your name:

Email address:

Website URL:

Please leave a comment:

You agree that this form is for A N T I-S P A M B O T S!
     D O-N O T-S U B M I T !