Hardening Your Kamailio Configuration
Given the current landscape of DDOS (Distributed Denial of Service) attacks against VoIP service providers, it has never been more important to harden your telephony infrastructure against attacks. In this article, we are going to implement user-agent blocking, geographic-based IP blocking, and IP address allow listing. In this article, we will be utilizing Kamailio's antiflood
, auth
, geoip2
, htable
, permissions
, pike
, registrar
, secfilter
, and usrloc
module. In a later article, we will explore utilizing iptables
to block malicious SIP messages.
Prerequisites
-
Your Kamailio instance is connected to a database backend.
-
Your Kamailio database has the
secfilter
table -
You have
kamailio-geoip2-modules
installed -
You have downloaded the
GeoLite2
database- Place the
GeoLite2-Country.mmdb
file in the following directory:/etc/kamailio
- Place the
-
You have the
kamcmd
utility properly configured- You can signup for and download the database by visiting this link.
Step 1 — Add directives to your kamailio.cfg
file
At the top of your kamailio.cfg
file, below #!KAMAILIO
we will add five new directives (if they do not already exist):
#!define WITH_GEOIP2
#!define WITH_SECFILTER
#!define WITH_ANTIFLOOD
#!define WITH_AUTH
#!define WITH_IPAUTH
Your Kamailio header should look something like the following:
#!KAMAILIO
#!define WITH_MYSQL
#!define WITH_GEOIP2
#!define WITH_SECFILTER
#!define WITH_ANTIFLOOD
#!define WITH_AUTH
#!define WITH_IPAUTH
Step 2 — Update your Modules Section
Update your #!ifdef WITH_MYSQL
section to the following:
#!ifdef WITH_MYSQL
loadmodule "db_mysql.so"
loadmodule "sqlops.so"
#!endif
Above the #!ifdef WITH_DEBUG
add the following:
#!ifdef WITH_GEOIP2
loadmodule "geoip2.so"
#!endif
#!ifdef WITH_SECFILTER
loadmodule "secfilter.so"
#!endif
Step 3 — Update your module-specific parameters
Add the following line to your #!ifdef WITH_ANTIFLOOD
section:
modparam("htable", "htable", "auth_try=>size=12;initval=0;autoexpire=86400")
Above the #!ifdef WITH_DEBUG
section add the following (update the DBURL with your database connection string otherwise it will not work):
#!ifdef WITH_GEOIP2
modparam("geoip2", "path", "/etc/kamailio/GeoLite2-Country.mmdb")
#!endif
#!ifdef WITH_SECFILTER
modparam("secfilter", "db_url", DBURL)
#!endif
# ---— sqlops params -----
modparam("sqlops","sqlcon", "cb=>DBURL")
Step 4 — Update route[REQINIT]
section
Update your route[REQINIT]
section to the following:
# Per SIP request initial checks
route[REQINIT] {
# No connect for sending replies
set_reply_no_connect();
# Enforce symmetric signaling
# Send back replies to the source address of request
force_rport();
#!ifdef WITH_SECFILTER
# Check for SQL Injection attacks
# Search for illegal characters in several headers (user-agent, from, to and contact).
# If illegal characters are found the message will be dropped.
secf_check_sqli_all();
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), passed sql injection attack test\n");
#!ifdef WITH_GEOIP2
# Identify the source of the messsage via IP address identification
# If the IP Address is not from the US, but is contained in the "address" table then the message will be accepted.
if (geoip2_match("$si", "src")) {
secf_check_country($gip2(src=>cc));
# return values ...
# 2 = allowlisted
# 1 = not found
# -1 = error
# -2 = blacklisted
if ($? != 2 && !allow_source_address_group()) {
xinfo("[REQINIT]: $rm from $si blocked for call-id: $(ci), source IP country '$gip2(src=>cc)' is not allowlisted\n");
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), source IP country '$gip2(src=>cc)' is allowlisted or is from an approved IP\n");
}
#!endif
# Identify the user agent of the message via user-agent header
# If the user agent is on the block list, it will be dropped.
secf_check_ua();
if ($? == -2) {
# return values ...
# 2 = allowlisted
# 1 = not found
# -1 = error
# -2 = blacklisted
xinfo("[REQINIT]: $rm from $si blocked for call-id: $(ci), useragent '$ua' is blocklisted\n");
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), useragent '$ua' is not blocklisted\n");
#!endif
#!ifdef WITH_ANTIFLOOD
# Flood detection from same IP and traffic ban for a while
if(src_ip!=myself && !allow_source_address_group()) {
if($sht(ipban=>$si)!=$null) {
# ip is already blocked
xinfo("[REQINIT]: $rm from $si blocked for call-id: $(ci), request from blocked IP\n");
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), request from a unblocked IP\n");
if (!pike_check_req()) {
xinfo("[REQINIT]: $rm from $si blocked for call-id: $(ci), pike blocking\n");
$sht(ipban=>$si) = 1;
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), pike not blocking\n");
}
if($sht(auth_try=>$si) > 20){
xinfo("[REQINIT]: $rm from $si blocked for call-id: $(ci), exceeds register attempts\n");
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), current register attempts = $sht(auth_try=>$si) for $si\n");
#!endif
if (!sanity_check()) {
xinfo("[REQINIT]: $rm from $si blocked for call-id: $(ci), malformed SIP request\n");
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), request is sane\n");
if (!mf_process_maxfwd_header("10")) {
xinfo("[REQINIT]: $rm from $si blocked for call-id: $(ci), too many hops\n");
sl_send_reply("483","Too Many Hops");
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), passed hop test\n");
# Hostname in contact
if($sel(contact.uri.host) =~ "^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$") {
xinfo("[REQINIT]: $rm from $si blocked for call-id: $(ci), using IP instead of dns for hostname\n");
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), hostname is not IP\n");
# Check supported methos
if (!is_method("REGISTER|INVITE|ACK|BYE|CANCEL|INFO|UPDATE|OPTIONS")) {
sl_send_reply("405", "Method not allowed");
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), method is allowed\n");
if(is_method("OPTIONS")) {
# Only reply to option messages if the endpoint or the carrier is defined
if (allow_source_address_group()) {
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), sent from allowed address group, responding with OPTIONS\n");
sl_send_reply("200","Keepalive");
exit;
}
#!ifdef WITH_USRLOCDB
# We need to find out if the location of the Options request is from a registered Endpoint
if (sql_xquery("cb", "select * from location where contact like '%$si%'", "ra")) {
if ($dbr(ra=>rows) == 0){
xinfo("[REQINIT]: $rm from $si blocked for call-id: $(ci), is not from a registered endpoint\n");
sql_result_free("ra");
exit;
}
xinfo("[REQINIT]: $rm from $si allowed for call-id: $(ci), is from a registered endpoint\n");
sl_send_reply("200","Keepalive");
sql_result_free("ra");
exit;
}
#!endif
}
}
Step 5 — Add user agents to the block-list
Let's add some of my favorite User-Agents to immediately block. At the command line (BASH), execute the following commands:
kamcmd secfilter.add_bl ua friendly-scanner
kamcmd secfilter.add_bl ua sipcli/
kamcmd secfilter.add_bl ua VaxSIPUserAgent/
kamcmd secfilter.add_bl ua pplsip
kamcmd secfilter.add_bl ua system
kamcmd secfilter.add_bl ua exec.
kamcmd secfilter.add_bl ua sipsak
kamcmd secfilter.add_bl ua sipvicious
kamcmd secfilter.add_bl ua siparmyknife
kamcmd secfilter.add_bl ua VaxIPUserAgent
kamcmd secfilter.add_bl ua SIParator/
kamcmd secfilter.add_bl ua eyebeam
Step 6 — Add Countries to your allow-list
Add the two letter country code for countries you expect to receive traffic from:
kamcmd secfilter.add_wl country US
Step 7 — Add IP Addresses to your address
table
This is where you want to add your upstream carrier's static IPs and your customer's static IPs.
Assuming Group 1
for Carriers and Group 2
for Customers (Groups are arbitrary and can be whatever you like to differentiate them), add your carriers and customers to the address table similar to the following:
insert into address (id, grp, ip_addr, mask, port, tag) values (1, 1, '1.1.1.1', 32, 0, 'Carrier');
insert into address (id, grp, ip_addr, mask, port, tag) values (2, 2, '2.2.2.2', 32, 0, 'Customer');
Step 8 — Restart Kamailio
Go ahead and restart your Kamailio instance
systemctl restart kamailio
Code Review
If you would like to review a complete example of this, please view my GitHub Gist.