2015.05.05 19:28
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.
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
.
As a side note, check out my tutorial on how to get your Google Voice call history with detailed minutes usage.
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.
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
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.
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 MYSIP
and 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.
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!
This section provides some additional tips that may be useful.
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 restart
before 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.
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.
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".
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
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 Linux, Android, iPhone), 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
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.