The Proxy-Call Session Control Function is the first network element a UE sends it’s SIP REGISTER message to, but how does it get there?
To begin with our UE connects as it would normally, getting a default bearer, an IP address and connectivity.
Overview
If the USIM has an ISIM application on it (or IMS is enabled on the UE using USIM for auth) and an IMS APN exists on the UE for IMS, the UE will set up another bearer in addition to the default bearer.
This bearer will carry our IMS traffic and allow QoS to be managed through the QCI values set on the bearer.
While setting up the bearer the UE requests certain parameters from the network in the Protocol Configuration Options element, including the P-CSCF address.
When setting up the bearer the network responds with this information, which if supported includes the P-CSCF IPv4 &/or IPv6 addresses.
The Message Exchange
We’ll start assuming the default bearer is in place & our UE is configured with the APN for IMS and supports IMS functionality.
The first step is to begin the establishment of an additional bearer for the IMS traffic.
This is kicked off through the Uplink NAS Transport, PDN Connectivity Request from the UE to the network. This includes the IMS APN information, and the UE’s NAS Payload includes the Protocol Configuration Options element (PCO), with a series of fields the UE requires responses from the network. including DNS Server, MTU, etc.
In the PCO the UE also includes the P-CSCF address request, so the network can tell the UE the IP of the P-CSCF to use.
If this is missing it’s because either your APN settings for IMS are not valid, or your device doesn’t have IMS support or isn’t enabling it.(that could be for a few reasons).
The MME gets this information from the P-GW, and the network responds in the E-RAB Setup Request, Activate default EPS bearer Context Request and includes the Protocol Configuration Options again, this time the fields are populated with their respective values, including the P-CSCF Address;
Once the UE has this setup, the eNB confirms it’s setup the radio resources through the E-RAB Setup Response.
One the eNB has put the radio side of things in place, the UE confirms the bearer assignment has completed successfully through the Uplink NAS Transport, Activate default EPS Bearer Accept, denoting the bearer is now in place.
Now the UE has the IP address(s) of the P-CSCF and a bearer to send it over, the UE establishes a TCP socket with the address specified in the P-CSCF IPv4 or IPv6 address, to start communicating with the P-CSCF.
The SIP REGISTER request can now be sent and the REGISTRATION procedure can begin.
IPsec ESP can be used in 3 different ways on the Gm interface between the Ue and the P-CSCF:
Integrity Protection – To prevent tampering
Ciphering – To prevent inception / eavesdropping
Integrity Protection & Ciphering
On Wireshark, you’ll see the ESP, but you won’t see the payload contents, just the fact it’s an Encapsulated Security Payload, it’s SPI and Sequence number.
By default, Kamailio’s P-CSCF only acts in Integrity Protection mode, meaning the ESP payloads aren’t actually encrypted, with a few clicks we can get Wireshark to decode this data;
Just open up Wireshark Preferences, expand Protocols and jump to ESP
Now we can set the decoding preferences for our ESP payloads,
In our case we’ll tick the “Attempt to detect/decode NULL encrypted ESP payloads” box and close the box by clicking OK button.
Now Wireshark will scan through all the frames again, anything that’s an ESP payload it will attempt to parse.
Now if we go back to the ESP payload with SQN 1 I showed a screenshot of earlier, we can see the contents are a TCP SYN.
Now we can see what’s going on inside this ESP data between the P-CSCF and the UE!
As a matter of interest if you can see the IK and CK values in the 401 response before they’re stripped you can decode encrypted ESP payloads from Wireshark, from the same Protocol -> ESP section you can load the Ciphering and Integrity keys used in that session to decrypt them.
While poking around the development and debugging features on Samsung handsets I found the ability to run IMS Debugging directly from the handset.
Alas, the option is only available in the commercial version, it’s just there for carriers, and requires a One Time Password to unlock.
When tapping on the option a challenge is generated with a key.
Interestingly I noticed that the key changes each time and can reject you even in aeroplane mode, suggesting the authentication happens client side.
Some research revealed you can pull APKs off an Android phone, so I downloaded a utility called “APK Extractor” from the Play store, and used it to extract the Samsung Sysdump utility.
So now I was armed with the APK on my local machine, the next step was to see if I could decompile the APK back into source code.
Some Googling found me an online APK decompiler, which I fed the compiled APK file and got back the source code.
I did some poking around inside the source code, and then I found an interesting directory:
Here’s a screenshot of the vanilla code that came out of the app.
I’m not a Java expert, but even I could see the “CheckOTP” function and understand that that’s what validates the One Time Passwords.
The while loop threw me a little – until I read through the rest of the code; the “key” in the popup box is actually a text string representing the current UNIX timestamp down to the minute level. The correct password is an operation done on the “key”, however the CheckOTP function doesn’t know the challenge key, but has the current time, so generates a challenge key for each timestamp back a few minutes and a few minutes into the future.
I modified the code slightly to allow me to enter the presented “key” and get the correct password back. It’s worth noting you need to act quickly, enter the “key” and enter the response within a minute or so.
In the end I’ve posted the code on an online Java compiler,
Samsung handsets have a feature built in to allow debugging from the handset, called Sysdump.
Entering *#9900# from the Dialing Screen will bring up the Sysdump App, from here you can dump logs from the device, and run a variety of debugging procedures.
But for private LTE operators, the two most interesting options are by far the TCPDUMP START option and IMS Logger, but both are grayed out.
Tapping on them asks for a one-time password and has a challenge key.
These options are not available in the commercial version of the OS and need to be unlocked with a one time key generated by a tool Samsung for unlocking engineering firmware on handsets.
Luckily this authentication happens client side, which means we can work out the password it’s expecting.
Once you’ve entered the code and successfully unlocked the IMS Debugging tool there’s a few really cool features in the hamburger menu in the top right.
DM View
This shows the SIP / IMS Messaging and the current signal strength parameters (used to determine which RAN type to use (Ie falling back from VoLTE to UMTS / Circuit Switched when the LTE signal strength drops).
Tapping on the SIP messages expands them and allows you to see the contents of the SIP messages.
Interesting the actual nitty-gritty parameters in the SIP headers are missing, replaced with X for anything “private” or identifiable.
Luckily all this info can be found in the Pcap.
The DM View is great for getting a quick look at what’s going on, on the mobile device itself, without needing a PC.
Logging
The real power comes in the logging functions,
There’s a lot of logging options, including screen recording, TCPdump (as in Packet Captures) and Syslog logging.
From the hamburger menu we can select the logging parameters we want to change.
From the Filter Options menu we can set what info we’re going to log,
After a few quiet months I’m excited to say I’ve pushed through some improvements recently to PyHSS and it’s growing into a more usable HSS platform.
MongoDB Backend
This has a few obvious advantages – More salable, etc, but also opens up the ability to customize more of the subscriber parameters, like GBR bearers, etc, that simple flat text files just wouldn’t support, as well as the obvious issues with threading and writing to and from text files at scale.
Knock knock.
Race condition.
Who’s there?
— Threading Joke.
For now I’m using the Open5GS MongoDB schema, so the Open5Gs web UI can be used for administering the system and adding subscribers.
The CSV / text file backend is still there and still works, the MongoDB backend is only used if you enable it in the YAML file.
The documentation for setting this up is in the readme.
SQN Resync
If you’re working across multiple different HSS’ or perhaps messing with some crypto stuff on your USIM, there’s a chance you’ll get the SQN (The Sequence Number) on the USIM out of sync with what’s on the HSS.
This manifests itself as an Update Location Request being sent from the UE in response to an Authentication Information Answer and coming back with a Re-Syncronization-Info AVP in the Authentication Info AVP. I’ll talk more about how this works in another post, but in short PyHSS now looks at this value and uses it combined with the original RAND value sent in the Authentication Information Answer, to find the correct SQN value and update whichever database backend you’re using accordingly, and then send another Authentication Information Answer with authentication vectors with the correct SQN.
SQN Resync is something that’s really cryptographically difficult to implement / confusing, hence this taking so long.
What’s next? – IMS / Multimedia Auth
The next feature that’s coming soon is the Multimedia Authentication Request / Answer to allow CSCFs to query for IMS Registration and manage the Cx and Dx interfaces.
Code for this is already in place but failing some tests, not sure if that’s to do with the MAA response or something on my CSCFs,
LTE has great concepts like NAS that abstract the actual transport layers, so the NAS packet is generated by the UE and then read by the MME.
One thing that’s a real headache about private LTE is the authentication side of things. You’ll probably bash your head against a SIM programmer for some time.
As your probably know when connecting to a network, the UE shares it’s IMSI / TIMSI with the network, and the MME requests authentication information from the HSS using the Authentication Information Request over Diameter.
The HSS then returns a random value (RAND), expected result (XRES), authentication token (AUTN) and a KASME for generating further keys,
The RAND and AUTN values are sent to the UE, the USIM in the UE calculates the RES (result) and sends it back to the MME. If the RES value received by the MME is equal to the expected RES (XRES) then the subscriber is mutually authenticated.
Using this tool I was able to plug a USIM into my USIM reader, using the Diameter client built into PyHSS I was able to ask for Authentication vectors for a UE using the Authentication Information Request to the HSS and was sent back the Authentication Information Answer containing the RAND and AUTN values, as well as the XRES value.
Then I used the osmo-sim-auth app to query the RES and RAND values against the USIM.
The RES I got back matched the XRES, meaning the HSS and the USIM are in sync (SQNs match) and they mutually authenticated.
I thought I’d expand a little on how the Crypto side of things works in LTE & NR (also known as 4G & 5G).
Authentication primarily happens in two places, one at each end of the network, the Home Subscriber Server and in the USIM card. Let’s take a look at each of them.
On the USIM
On the USIM we’ve got two values that are entered in when the USIM is provisioned, the K key – Our secret key, and an OPc key (operator key).
These two keys are the basis of all the cryptography that goes on, so should never be divulged.
The only other place to have these two keys in the HSS, which associates each K key and OPc key combination with an IMSI.
The USIM also stores the SQN a sequence number, this is used to prevent replay attacks and is incremented after each authentication challenge, starting at 1 for the first authentication challenge and counting up from there.
On the HSS
On the HSS we have the K key (Secret key), OPc key (Operator key) and SQN (Sequence Number) for each IMSI on our network.
Each time a IMSI authenticates itself we increment the SQN, so the value of the SQN on the HSS and on the USIM should (almost) always match.
Authentication Options
Let’s imagine we’re designing the authentication between the USIM and the Network; let’s look at some options for how we can authenticate everyone and why we use the process we use.
Failed Option 1 – Passwords in the Clear
The HSS could ask the USIM to send it’s K and OPc values, compare them to what the HSS has in place and then either accept or reject the USIM depending on if they match.
The obvious problem with this that to send this information we broadcast our supposedly secret K and OPc keys over the air, so anyone listening would get our secret values, and they’re not so secret anymore.
This is why we don’t use this method.
Failed Option 2 – Basic Crypto
So we’ve seen that sending our keys publicly, is out of the question.
The HSS could ask the USIM to mix it’s K key and OPc key in such a way that only someone with both keys could unmix them.
This is done with some cryptographic black magic, all you need to know is it’s a one way function you enter in values and you get the same result every time with the same input, but you can’t work out the input from the result.
The HSS could then get the USIM to send back the result of mixing up both keys, mix the two keys it knows and compare them.
The HSS mixes the two keys itself, and get’s it’s own result called XRES (Expected Result). If the RES (result) of mixing up the keys by the USIM is matches the result when the HSS mixes the keys in the same way (XRES (Expected Result)), the user is authenticated.
The result of mixing the keys by the USIM is called RES (Result), while the result of the HSS mixing the keys is called XRES (Expected Result).
This is abetter solution but has some limitations, because our special mixing of keys gets the same RES each time we put in our OPc and K keys each time a subscriber authenticates to the network the RES (result) of mixing the keys is going to be the same.
This is vulnerable to replay attacks. An attacker don’t need to know the two secret keys (K & OPc) that went into creating the RES (resulting output) , the attacker would just need to know the result of RES, which is sent over the air for anyone to hear. If the attacker sends the same RES they could still authenticate.
This is why we don’t use this method.
Failed Option 3 – Mix keys & add Random
To prevent these replay attacks we add an element of randomness, so the HSS generates a random string of garbage called RAND, and sends it to the USIM.
The USIM then mixes RAND (the random string) the K key and OPc key and sends back the RES (Result).
Because we introduced a RAND value, every time the RAND is different the RES is different. This prevents against the replay attacks we were vulnerable to in our last example.
If the result the USIM calculated with the K key, OPc key and random data is the same as the USIM calculated with the same K key, OPc key and same random data, the user is authenticated.
While an attacker could reply with the same RES, the random data (RAND) will change each time the user authenticates, meaning that response will be invalid.
While an attacker could reply with the same RES, the random data (RAND) will change each time the user authenticates, meaning that response will be invalid.
The problem here is now the network has authenticated the USIM, the USIM hasn’t actually verified it’s talking to the real network.
This is why we don’t use this method.
GSM authentication worked like this, but in a GSM network you could setup your HLR (The GSM version of a HSS) to allow in every subscriber regardless of what the value of RES they sent back was, meaning it didn’t look at the keys at all, this meant attackers could setup fake base stations to capture users.
Option 4 – Mutual Authentication (Real World*)
So from the previous options we’ve learned:
Our network needs to authenticate our subscribers, in a way that can’t be spoofed / replayed so we know who to bill & where to route traffic.
Our subscribers need to authenticate the network so they know they can trust it to carry their traffic.
So our USIM needs to authenticate the network, in the same way the network authenticates the USIM.
To do this we introduce a new key for network authentication, called AUTN.
The AUTN key is generated by the HSS by mixing the secret keys and RAND values together, but in a different way to how we mix the keys to get RES. (Otherwise we’d get the same key).
This AUTN key is sent to the USIM along with the RAND value. The USIM runs the same mixing on it’s private keys and RAND the HSS did to generate the AUTN , except this is the USIM generated – An Expected AUTN key (XAUTN). The USIM compares XAUTN and AUTN to make sure they match. If they do, the USIM then knows the network knows their secret keys.
The USIM then does the same mixing it did in the previous option to generate the RES key and send it back.
The network has now authenticated the subscriber (HSS has authenticated the USIM via RES key) and the subscriber has authenticated the USIM (USIM authenticates HSS via AUTN key).
*This is a slightly simplified version of how EUTRAN / LTE authentication works between the HSS and the USIM – In reality there are a few extra values, such as SQN to take into consideration and the USIM talks to to the MME not the HSS directly.
I’ll do a follow up post covering the more nitty-gritty elements, AMF and SQN fields, OP vs OPc keys, SQN Resync, how this information is transfered in the Authentication Information Answer and how KASME keys are used / distributed.
I started working on a private LTE project a while ago; RAN hardware (eNodeBs) were on the way, down to a shortlist of a few EPC platforms, but I still needed USIMs before anyone was connecting to the network.
So why are custom USIMs a requirement? Can’t you just use any old USIM/SIMs?
For roaming to work between carriers they’ve got to have their HSS / DRA connecting to the DRA or HSS of other carriers, to allow roaming subscribers to access the network, otherwise they too would fall foul of the mutual network authentication and the USIM wouldn’t connect to the network.
The first USIMs I purchased online through a popular online marketplace with a focus on connecting you to Chinese manufacturers. They listed a package of USIMS, a USB reader/writer that supported all the standard USIM form factors and the software to program it, which I purchased.
The USIMs worked fairly well – They are programmable via a card reader and software that, although poorly translated/documented, worked fairly well.
K and OP/OPc values could be written to the card but not read, while the other values could be read and written from the software, the software also has the ability to sequentially program the USIMs to make bulk operations easier. The pricing worked out about $8 USD per USIM, which although expensive for the quantity and programmable element is pretty reasonable.
Every now and then the Crypto values for some reason or another wouldn’t get updated, which is exactly as irritating as it sounds.
Pretty quickly into the build I learned the USIMs didn’t include an ISIM service on the card, ISIM being the service that runs on the UCCID responsible for IMS / VoLTE authentication.
Again I went looking and reached out to a few manufacturers of USIMs.
The big vendors, Gemalto, Kona, etc, weren’t interested in providing USIMs in quantities less than 100,000 and their USIMs came from the factory pre-programmed, meaning the values could only be changed through remote SIM provisioning, a form of black magic.
In the end I reached out to an OEM manufacturer from China who provided programmable USIM / ISIMs for less than I was paying on the online marketplace and at any quantity I wanted with custom printing options, allocated ICCIDs, etc.
The non-programmable USIMs worked out less than $0.40 USD each in larger quantities, and programmable USIM/ISIMs for about $5 USD.
The software was almost identical except for the additional tab for ISIM operations.
Smart Card Readers
In theory this software and these USIMs could be programmed by any smart card reader.
In practice, the fact that the ISO standard smart card is the same size as a credit card, means most smart card readers won’t fit the bill.
I tried a few smart card readers, from the one built into my Thinkpad, to a Bluedrive II from one of the USIM vendors, in the end the MCR3516 Smart Card Reader which reads 4FF USIMs (Standard ISO size smart card, full size SIM, Micro SIM and Nano SIM form factors, which saved on so much mucking about with form factor adapters etc.
Future Projects
I’ve got some very calls “Multi Operator Neutral Host” (MoNEH) USIMs from the guys at Telet Research I’m looking forward to playing with,
eSIMs are on my to-do list too, and the supporting infrastructure, as well as Over the Air updating of USIMs.
Now we’ll put both together to create something functional you could use in your own deployments. (You’d often find it’s faster to use HTable to store and retrieve data like this, but that’s a conversation for another day)
The Project
We’ll build a SIP honeypot using Kamailio. It’ll listen on a Public IP address for SIP connections from people scanning the internet with malicious intent and log their IPs, so our real SIP softswitches know to ignore them.
We’ll use GeoIP2 to lookup the location of the IP and then store that data into a MySQL database.
Lastly we’ll create a routing block we can use on another Kamailio instance to verify if that the IP address of the received SIP message is not in our blacklist by searching the MySQL database for the source IP.
The Database
In this example I’m going to create a database called “blacklist” with one table called “baddies”, in MySQL I’ll run:
CREATE database blacklist;
CREATE TABLE `baddies` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`ip_address` INT unsigned UNIQUE,
`hits` INT,
`last_seen` DATETIME,
`ua` TEXT,
`country` TEXT,
`city` TEXT
);
I’ll setup a MySQL user to INSERT/UPDATE/SELECT data from the MySQL database.
For storing IP addresses in the database we’ll store them as unsigned integers, and then use the INET_ATON('127.0.0.1') MySQL command to encode them from dotted-decimal format, and the INET_NTOA('2130706433') to put them back into dotted decimal.
Modparams
Now we’ll need to configure Kamailio, I’ll continue on from where we left off in the last post on GeoIP2 as we’ll use that to put Geographic data about the IP before adding the MySQL and SQLOps modules:
# ----- SQL params -----
loadmodule "db_mysql.so"
loadmodule "sqlops.so"
#Create a new MySQL database connection called blacklist_db
modparam("sqlops","sqlcon","blacklist_db=>mysql://root:yourpassword@localhost/blacklist")
#Set timeouts for MySQL Connections
modparam("db_mysql", "ping_interval", 60)
modparam("db_mysql", "auto_reconnect", 1)
modparam("db_mysql", "timeout_interval", 2)
After loading db_mysql and sqlops we create a new object / connection called blacklist_db with our MySQL Database parameters.
Now after a restart we’ll be connected to our MySQL database.
Honeypot Routing Logic
Now we’ll create a route to log the traffic:
####### Routing Logic ########
/* Main SIP request routing logic
* - processing of any incoming SIP request starts with this route
* - note: this is the same as route { ... } */
request_route {
route(AddToBlacklist);
sl_reply('200', 'Sure thing boss!');
}
route[AddToBlacklist]{
xlog("Packet received from IP $si");
sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (2130706433, 10, NOW(), 'testua2', 'Australia', 'Hobart');");
}
Now for each SIP message received a new record will be inserted into the database:
root@ip-172-31-8-156:/etc/kamailio# mysql -u root -p blacklist -e "select * from baddies;"
Enter password:
+----+------------+------+---------------------+---------+-----------+--------+
| id | ip_address | hits | last_seen | ua | country | city |
+----+------------+------+---------------------+---------+-----------+--------+
| 1 | 2130706433 | 10 | 2019-08-13 02:52:57 | testua2 | Australia | Hobart |
| 2 | 2130706433 | 10 | 2019-08-13 02:53:01 | testua2 | Australia | Hobart |
| 3 | 2130706433 | 10 | 2019-08-13 02:53:05 | testua2 | Australia | Hobart |
+----+------------+------+---------------------+---------+-----------+--------+
This is great but we’re not actually putting the call variables in here, and we’ve got a lot of duplicates, let’s modify our sql_xquery() to include the call variables:
Now we’re setting the IP Address value to the Source IP psedovariable ($si) and formatting it using the INET_ATON function in MySQL, setting the last_seen to the current timestamp and setting the user agent to the User Agent psedovariable ($ua).
Let’s restart Kamailio, truncate the data that’s currently in the DB, send some SIP traffic to it and then check the contents:
mysql -u root -p blacklist -e "select *, INET_NTOA(ip_address) from baddies;"
Here you can see we’re starting to get somewhere, the IP, UA and last_seen values are all now correct.
We’re getting multiple entries from the same IP though, instead we just want to increment the hits counter and set the last_seen to the current time, for that we’ll just update the SQL query to set the time to be NOW() and if that IP is already in the database to update the last_seen value and incriment the hits counter:
route[AddToBlacklist]{
xlog("Packet received from IP $si");
geoip2_match("$si", "src"))
sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (INET_ATON('$si'), 1, NOW(), '$ua', '$gip2(src=>cc)', '$gip2(src=>city)') ON DUPLICATE KEY UPDATE last_seen = NOW(), hits = hits + 1;", "r_sql");
}
The only issue with this is if GeoIP2 doesn’t have a match, no record will be added in the database, so we’ll add a handler for that:
route[AddToBlacklist]{
xlog("Packet received from IP $si");
if(geoip2_match("$si", "src")){
sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (INET_ATON('$si'), 1, NOW(), '$ua', '$gip2(src=>cc)', '$gip2(src=>city)') ON DUPLICATE KEY UPDATE last_seen = NOW(), hits = hits + 1;", "r_sql");
}else{ ##If no match in GeoIP2 leave Country & City fields blank
sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (INET_ATON('$si'), 1, NOW(), '$ua', '', '') ON DUPLICATE KEY UPDATE last_seen = NOW(), hits = hits + 1;", "r_sql");
}
}
Now let’s check our database again and see how the data looks:
mysql -u root -p blacklist -e "select *, INET_NTOA(ip_address) from baddies;"
Perfect! Now we’re inserting data into our blacklist from our honeypot. Now we’ll configure a new routing block we can use on another Kamailio instance to see if an IP is in the blacklist.
I left this running on my AWS box for a few hours, and lots of dodgy UAs dropped in to say hello, one of which was very insistent on making calls to Poland…
Querying the Data
Now we’ve got a blacklist it’s only useful if we block the traffic from our malicous actors who we’ve profiled in the database.
You could feed this into BGP to null route the traffic, or hook this into your firewall’s API, but we’re going to do this in Kamailio, so we’ll create a new routing block we can use on a different Kamailio instance – Like a production one – to see if the IP it just received traffic from is in the blacklist.
We’ve already spoken about querying databases in the SQLops Kamailio bytes, but this routing block will query the blacklist database, and if the sender is in the database, one or more records will be returned, so we know they’re bad and will drop their traffic:
route[CheckBlacklist]{
xlog("Checking blacklist for ip $si");
#Define a variable containing the SQL query we'll run
$var(sql) = "select INET_NTOA(ip_address) as ip_address from baddies;";
#Log the SQL query we're going to run to syslog for easy debugging
xlog("Query to run is $var(sql)");
#Query blacklist_db running the query stored in $var(sql) and store the result of the query to result_sql
sql_query("blacklist_db", "$var(sql)", "result_sql");
#If more than 0 records were returned from the database, drop the traffic
if($dbr(result_sql=>rows)>0){
xlog("This guy is bad news. Dropping traffic from $si");
exit;
}else{
xlog("No criminal record for $si - Allowing to progress");
}
}
This Honeypot use case just put those elements together.
In reality a far better implementation of this would use HTable to store this data, but hopefully this gives you a better understanding of how to actually work with data.
Final Note
I wrote this post about a week ago, and left the config running on an AWS box. I was getting hits to it within the hour, and in the past week I’ve had 172 IPs come and say hello, and some like the FriendlyScanner instance at 159.65.220.215 has sent over 93,000 requests:
If you’re planning on using this in production you probably want to automate the pulling of this data on a regular basis and keep it in a different directory.
I’ve made a very simple example Kamailio config that shows off some of the features of GeoIP2’s logic and what can be shown, so let’s look at the basics of the module:
if(geoip2_match("$si", "src")){
xlog("Packet received from IP $si");
xlog("Country is: $gip2(src=>cc)\n");
}
If we put this at the top of our request_route block every time we recieve a new request we can see the country from which the packet came from.
Let’s take a look at the output of syslog (with my IP removed):
#> tail -f /var/log/syslog
ERROR: <script>: Packet received from IP 203.###.###.###
ERROR: <script>: Country is: AU
ERROR: <script>: City is: Melbourne
ERROR: <script>: ZIP is: 3004
ERROR: <script>: Regc is: VIC
ERROR: <script>: Regn is: Victoria
ERROR: <script>: Metro Code is: <null>
We can add a bunch more smarts to this and get back a bunch more variables, including city, ZIP code, Lat & Long (Approx), timezone, etc.
if(geoip2_match("$si", "src")){
xlog("Packet received from IP $si");
xlog("Country is: $gip2(src=>cc)\n");
xlog("City is: $gip2(src=>city)");
xlog("ZIP is: $gip2(src=>zip)");
xlog("Regc is: $gip2(src=>regc)");
xlog("Regn is: $gip2(src=>regn)");
xlog("Metro Code is: $gip2(src=>metro)");
if($gip2(src=>cc)=="AU"){
xlog("Traffic is from Australia");
}
}else{
xlog("No GeoIP Match for $si");
}
#> tail -f /var/log/syslog
ERROR: <script>: Packet received from IP ###.###.###.###
ERROR: <script>: Country is: AU
ERROR: <script>: City is: Melbourne
ERROR: <script>: ZIP is: 3004
ERROR: <script>: Regc is: VIC
ERROR: <script>: Regn is: Victoria
ERROR: <script>: Metro Code is: <null>
Using GeoIP2 you could use different rate limits for domestic users vs overseas users, guess the dialling rules based on the location of the caller and generate alerts if accounts are used outside their standard areas.
We’ll touch upon this again in our next post on RTPengine where we’ll use an RTPengine closes to the area in which the traffic originates.
The other day I got an SMS from my bank, one of the big 4 Australian Banks.
BANKNAME Alert: Block placed on card ending in XXXX, for suspicious transaction at ‘THING NICK PURCHASED ONLINE’ for $29.00 at 13:56. If genuine, reply ‘Yes’. If Fraud, reply ‘No’.
SMS from bank
They’d detected possible fraud on my card, and were asking me to confirm if it was me or not by texting back.
That is my correct card number, and as it happens I had made an online purchase that was what it was querying.
I was already at my computer, so out of curiosity, opened the SMS Gateway I use, and set the caller ID to be my mobile number (Because spoofing Caller ID is trivial) and replied to the number the bank sent me the SMS from.
My phone beeped again:
Thank you for confirming this transaction is genuine. The block will be removed from your card within the next 20 minutes. No further action is required.
SMS from Bank
So what’s the issue here?
The issue is if someone were to steal your card details, and know your mobile number, they could just keep texting the bank’s verification line the word “yes” from an SMS gateway spoofing your Caller ID, the bank won’t block it.
No system is foolproof, but it seems a bit short sighted by this bank.
Texting back a code would be a better solution, because it would allow you to verify the person texting received the original SMS, or cycling the caller IDs from a big pool would decrease the likelihood of this working
We’ve talked a bit in the past few posts about keys, K and all it’s derivatives, such as Kenc, Kint, etc.
Each of these is derived from our single secret key K, known only to the HSS and the USIM.
To minimise the load on the HSS, the HSS transfers some of the key management roles to the MME, without ever actually revealing what the secret key K actually is to the MME.
This means the HSS is only consulted by the MME when a UE/Terminal attaches to the network, and not each time it attaches to different cell etc.
When the UE/Terminal first attaches to the network, as outlined in my previous post, the HSS also generates an additional key it sends to the MME, called K-ASME.
K-ASME is the K key derived value generated by the HSS and sent to the MME. It sands for “Access Security Management Entity” key.
When the MME has the K-ASME it’s then able to generate the other keys for use within the network, for example the Kenb key, used by the eNodeB to generate the keys required for communications.
The USIM generates the K-ASME itself, and as it’s got the same input parameters, the K-ASME generated by the USIM is the same as that generated by the HSS.
The USIM can then give the terminal the K-ASME key, so it can generate the same Kenb key required to generate keys for complete communications.
We’ve already touched on how subscribers are authenticated to the network, how the network is authenticated to subscribers.
Those functions are done “in the clear” meaning anyone listening can get a copy of the data transmitted, and responses could be spoofed or faked.
To prevent this, we want to ensure the data is ciphered (encrypted) and the integrity of the data is ensured (no one has messed with our packets in transmission or is sending fake packets).
Ciphering of Messages
Before being transmitted over the Air interface (Uu) each packet is encrypted to prevent eavesdropping.
This is done by taking the plain text data and a ciphering sequence for that data of the same length as the packet and XORing two.
The terminal and the eNodeB both generate the same ciphering sequence for that data.
This means to get the ciphered version of the packet you simply XOR the Ciphering Sequence and the Plain text data.
To get the plain text from the ciphered packet you simply XOR the ciphered packet and ciphering sequence.
The Ciphering Sequence is made up of parts known only to the Terminal and the Network (eNB), meaning anyone listening can’t deduce the same ciphering sequence.
The Ciphering Sequence is derived from the following input parameters:
Key Kenc
Packet Number
Bearer Number
Direction (UL/DL)
Packet Size
Is is then ciphered using a ciphering algorithm, 3GPP define two options – AES or SNOW 3G. There is an option to not generate a ciphering sequence at all, but it’s not designed for use in production environments for obvious reasons.
Ciphering Sequences are never reused, the packet number increments with each packet sent, and therefore a new Cipher Sequence is generated for each.
Someone listening to the air interface (Uu) may be able to deduce packet size, direction and even bearer, but without the packet number and secret key Kenc, the data won’t be readable.
Data Integrity
By using the same ciphering sequence & XOR process outlined above, we also ensure that data has not been manipulated or changed in transmission, or that it’s not a fake message spoofing the terminal or the eNB.
Each frame contains the packet and also a “Message Authentication Code” or “MAC” (Not to be confused with media access control), a 32 bit long cryptographic hash of the contents of the packet.
The sender generates the MAC for each packet and appends it in the frame,
The receiver looks at the contents of the packet and generates it’s own MAC using the same input parameters, if the two MACs (Generated and received) do not match, the packet is discarded.
This allows the receiver to detect corrupted packets, but does not prevent a malicious person from sending their own fake packets,
To prevent this the MAC hash function requires other input parameter as well as the packet itself, such as the secret key Kint, packet number, direction and bearer.
By adding this we ensure that the packet was sourced from a sender with access to all this data – either the terminal or the eNB.
In my last post we discussed how the network authenticated a subscriber, now we’ll look at how a subscriber authenticates to a network. There’s a glaring issue there in that the MME could look at the RES and the XRES and just say “Yup, OK” even if the results differed.
To combat this LTE networks have mutual authentication, meaning the network authenticates the subscribers as we’ve discussed, and the subscribers authenticate the network.
To do this our HSS will take the same random key (RAND) we used to authenticate the subscriber, and using a different cryptographic function (called g) take the RAND, the K value and a sequence number called SQN, and using these 3 inputs, generate a new result we’ll call AUTN.
The HSS sends the RAND (same as RAND used to authenticate the subscriber) and the output of AUTN to the MME which forwards it to the eNB to the UE which passes the RAND and AUTH values to the USIM.
The USIM takes the RAND and the K value from the HSS, and it’s expected sequence number. With these 3 values it applies the cryptographic function g generates it’s own AUTN result.
If it matches the AUTN result generated by the HSS, the USIM has authenticated the network.
I’ve been working on private LTE recently, and one of the first barriers you’ll hit will be authentication.
LTE doesn’t allow you to just use any SIM to authenticate to the network, but instead relies on mutual authentication of the UE and the network, so the Network knows it’s talking to the right UE and the UE knows it’s talking to the right network.
So because of this, you have to have full control over the SIM and the network. So let’s take a bit of a dive into USIMs.
So it’s a SIM card right?
As a bit of background; the ever shrinking card we all know as a SIM is a “Universal integrated circuit card” – a microcontroller with it’s own OS that generally has the ability to run Java applets.
One of the Java applets on the card / microcontroller will be the software stack for a SIM, used in GSM networks to authenticate the subscriber.
For UMTS and LTE networks the card would have a USIM software stack allowing it to act as a USIM, the evolved version of the SIM.
Because it’s just software a single card can run both a USIM and SIM software stack, and most do.
As I’m building an LTE network we’ll just talk about the USIM side of things.
USIM’s role in Authentication
When you fire up your mobile handset the baseband module in it communicates with the USIM application on the card.
When it comes time to authenticate to the network, and authenticate the network itself, the baseband module sends the provided challenge information from the network to the USIM which does the crypto magic to generate responses to the authentication challenges issued by the network, and the USIM issues it’s own challenges to the network.
The Baseband module provides the ingredients, but the USIM uses it’s secret recipe / ingredients combo, known only to the USIM and HSS, to perform the authentication.
Because the card challenges the network it means we’ve got mutual authentication of the network.
This prevents anyone from setting up their own radio network from going all Lionel Ritche and saying “Hello, is it me you’re looking for” and having all the UEs attach to the malicious network. (Something that could be done on GSM).
It’s worth noting too that because the USIM handles all this the baseband module, and therefore the mobile handset itself, doesn’t know any of the secret sauce used to negotiate with the network. It just gets the challenge and forwards the ingredients down to the USIM which spits back the correct response to send, without sharing the magic recipe.
This also means operators can implement their own Crypto functions for f and g, so long as the HSS and the USIM know how to generate the RES and AUTN results, it’ll work.
What’s Inside?
Let’s take a look at the information that’s stored on your USIM:
All the GSM stuff for legacy SIM application
Generally USIMs also have the ability to operate as SIMs in a GSM network, after all it’s just a different software stack. We won’t touch on GSM SIMs here.
ICCID
Because a USIM is just an application running on a Universal Integrated Circuit Card, it’s got a ICCID or Universal Integrated Circuit Card ID. Generally this is the long barcode / string of numbers printed on the card itself.
The network generally doesn’t care about this value, but operators may use it for logistics like shipping out cards.
PIN & PUK
PINs and PUKs are codes to unlock the card. If you get the PIN wrong too many times you need the longer PUK to unlock it.
These fields can be written to (when authenticated to the card) but not read directly, only challenged. (You can try a PIN, but you can’t see what it’s set too).
As we mentioned before the terminal will ask the card if that’s correct, but the terminal doesn’t know the PIN either.
IMSI
Each subscriber has an IMSI, an International Mobile Subscriber Identity.
IMSIs are hierarchical, starting with 3 digit Mobile Country Code MCC, then the Mobile Network Code (MNC) (2/3 digits) and finally a Mobile Subscription Identification Number (MSIN), a unique number allocated by the operator to the subscribers in their network.
This means although two subscribers could theoretically have the same MSIN they wouldn’t share the same MNC and MCC so the ISMI would still be unique.
The IMSI never changes, unless the subscriber changes operators when they’ll be issued a new USIM card by the new operator, with a different IMSI (differing MNC).
The MSIN isn’t the same as the phone number / MSISDN Number, but an IMSI generally has a MSISDN associated with it by the network. This allows you to port / change MSISDN numbers without changing the USIM/SIM.
K – Subscriber Key
Subscriber’s secret key known only to the Subscriber and the Authentication Center (AuC/ HSS).
All the authentication rests on the principle that this one single secret key (K) known only to the USIM and the AuC/HHS.
OP – Operator Code
Operator Code – same for all SIMs from a single operator.
Used in combination with K as an input for some authentication / authorisation crypto generation.
Because the Operator Code is common to all subscribers in the network, if this key were to be recovered it could lead to security issues, so instead OPc is generally used.
OPc – Operator Code (Derived)
Instead of giving each USIM the Operator Code a derived operator code can be precomputed when the USIM is written with the K key.
This means the OP is not stored on the USIM.
OPc=Encypt-Algo(OP,Key)
PLMN (Public Land Mobile Network)
The PLMN is the combination of MCC & MNC that identifies the operator’s radio access network (RAN) from other operators.
While there isn’t a specific PLMN field in most USIMs it’s worth understanding as several fields require a PLMN.
HPLMNwAcT (HPLMN selector with Access Technology)
Contains in order of priority, the Home-PLMN codes with the access technology specified.
This allows the USIM to work out which PLMN to attach to and which access technology (RAN), for example if the operator’s PLMN was 50599 we could have:
50599 E-UTRAN
50599 UTRAN
To try 4G and if that fails use 3G.
In situations where operators might partner to share networks in different areas, this could be set to the PLMN of the operator first, then it’s partnered operator second.
OPLMNwACT (Operator controlled PLMN selector with Access Technology)
This is a list of PLMNs the operator has a roaming agreement with in order of priority and with the access technology.
An operator may roam to Carrier X but only permit UTRAN access, not E-TRAN.
FEHPLMN (Equivalent HPLMN)
Used to define equivalent HPMNs, for example if two carriers merge and still have two PLMNs.
FPLMN (Forbidden PLMN list)
A list of PLMNs the subscriber is not permitted to roam to.
HPPLMN (Higher Priority PLMN search period)
How long in seconds to spend between each PLMN/Access Technology in HPLMNwAcT list.
ACC (Access Control Class)
The ACC allows values from 0-15, and determines the access control class of the subscriber.
In the UK the ACC values is used to restrict civilian access to cell phone networks during emergencies.
Ordinary subscribers have ACC numbers in the range 0 – 9. Higher priority users are allocated numbers 12-14.
During an emergency, some or all access classes in the range 0 – 9 are disabled.
This means service would be could be cut off to the public who have ACC value of 0-9, but those like first responders and emergency services would have a higher ACC value and the network would allow them to attach.
AD (Administrative Data)
Like the ACC field the AD field allows operators to drive test networks without valid paying subscribers attaching to the network.
The defined levels are:
’00’ normal operation.
’80’ type approval operations.
’01’ normal operation + specific facilities.
’81’ type approval operations + specific facilities.
’02’ maintenance (off line).
’04’ cell test operation.
GID 1 / 2 – Group Identifier
Two group identifier fields that allow the operator to identify a group of USIMs for a particular application.
SPN (Service Provider Name)
The SPN is an optional field containing the human-readable name of the network.
The SPN allows MVNOs to provide their own USIMs with their name as the operator on the handset.
ECC (Emergency Call Codes)
Codes up to 6 digits long the subscriber is allowed to dial from home screen / in emergency / while not authenticated etc.
MSISDN
Mobile Station International Subscriber Directory Number. The E.164 formatted phone number of the subscriber.
This is optional, as porting may overwrite this, so it doesn’t always match up.
HTable is Kamailio’s implimentation of Hash Tables a database-like data structure that runs in memory and is very quick.
It’s uses only become apparent when you’ve become exposed to it.
Let’s take an example of protecting against multiple failed registration attempts.
We could create a SQL database called registration attempts, and each time one failed log the time and attempted username.
Then we could set it so before we respond to traffic we query the database, find out how many rows there are that match the username being attempted and if it’s more than a threshold we set we send back a rate limit response.
The problem is that’s fairly resource intensive, the SQL data is read and written from disks and is slow to do both.
Enter HTable, which achieves the same thing with an in-memory database, that’s lightning fast.
Basic Setup
We’ll need to load htable and create an htable called Table1 to store data in:
$sht(MessageCount=>test) is the logical link to the Htable called MessageCount with a key named test. We’re making that equal itself + 1.
We’re then outputting the content of $sht(MessageCount=>test) to xlog too so we can see it’s value in Syslog.
Now each time a new dialog is started the MessageCount htable key “test” will be incremented.
We can confirm this in Syslog:
ERROR: : MessageCount is 1 ERROR: : MessageCount is 2
We can also check this in kamcmd too:
htable.dump MessageCount
Here we can see in MessageCount there is one key named “test” with a value of 6, and it’s an integer. (You can also store Strings in HTable).
So that’s all well and pointless, but let’s do make it a bit more useful, report on how many SIP transactions we get per IP. Instead of storing our values with the name key “test” we’ll name it based on the Source IP of the message, which lives in Psedovariable $si for Source IP Address.
I’m calling the boilerplate AUTH block, and I’ve added some logic to increment the AuthCount for each failed auth attempt, and reset it to $null if authentication is successful, thus resetting the counter for that IP Address.
Now we’ve done that we need to actually stop the traffic if it’s failed too many times. I’ve added the below check into REQINIT block, which I call at the start of processing:
if($sht(AuthCount=>$si) > 5){
xlog("$si is back again, rate limiting them...");
sl_send_reply("429", "Rate limiting");
exit;
}
Now if AuthCount is more than 5, it’ll respond with a Rate Limiting response.
Because in our modparam() setup for AuthCount we set an expiry, after 360 seconds (10 minutes), after 10 minutes all will be forgiven and our blocked UA can register again.
Advanced Usage / Notes
So now we’ve got Kamailio doing rate limiting, it’s probably worth mentioning the Pike module, which can also be used.
You’ll notice if you reboot Kamailio all the htable values are lost, that’s because the hashes are stored in memory, so aren’t persistent.
You have a few options for making this data persistent,
By using DMQ you can Sync data between Kamailio instances including htable values.
kamcmd can view, modify & manipulate htable values.
As we’ve seen before we can dump the contents of an htable using:
kamcmd htable.dump MessageCount
We can also add new entries & modify existing ones:
kamcmd htable.seti MessageCount ExampleAdd s:999
htable.seti is for setting integer values, we can also use htable.sets to set string values:
htable.sets MessageCount ExampleAdd Iamastring
We can also delete values from here too, which can be super useful for unblocking destinations manually:
htable.delete MessageCount ExampleAdd
As always code from this example is on GitHub. (Please don’t use it in production without modification, Authentication is only called on Register, and it’s just built upon the previous tutorials).
Caller-ID spoofing has been an issue in most countries since networks went digital.
SS7 doesn’t provide any caller ID validation facilities, with the assumption that everyone you have peered with you trust the calls from. So because of this it’s up to the originating switch to verify the caller ID selected by the caller is valid and permissible, something that’s not often implemented. Some SIP providers sell the ability to present any number as your CLI as a “feature”.
There’s heaps of news articles on the topic, but I thought it’d be worth talking about RFC4474 – Designed for cryptographically identifying users that originate SIP requests. While almost never used it’s a cool solution to a problem that didn’t take off.
It does this by adding a new header field, called Identity, for conveying a signature used for validating the identity of the caller, and Identity-Info for a reference to the certificate signing authority.
The calling proxy / UA creates a hash of it’s certificate, and inserts that into the SIP message in the Identity header.
The calling proxy / UA also inserts a “Identity-Info” header containing
The called party can then independently get the certificate, create it’s own hash of it, and if they match, then the identity of the caller has been verified.
Kamailio’s permissions module is simple to use, and we’ve already touched upon it in the security section in our Kamailio 101 series, but I thought I’d go over some of it’s features in more detail.
At it’s core, Kamailio’s Permissions module is a series of Access Control Lists (ACLs) that can be applied to different sections of your config.
We can manage permissions to do with call routing, for example, is that source allowed to route to that destination.
We can manage registration permissions, for example, is this subnet allowed to register this username.
We can manage URI permissions & address permissions to check if a specific SIP URI or source address is allowed to do something.
We’ll touch on a simple IP Address based ACL setup in this post, but you can find more information in the module documentation itself.
The Setup
We’ll be using a database backend for this (MySQL), setup the usual way.
We’ll need to load the permissions module and setup it’s basic parameters, for more info on setting up the database side of things have a look here.
Next we’ll need to add some IPs, we could use Serimis for this, or a straight MySQL INSERT, but we’ll use kamctl to add them. (kamcmd can reload addresses but doesn’t currently have the functionality to add them)
The above example we added a two new address entries,
The first one added a new entry in group 250 of “10.8.203.139”, with a /32 subnet mask (Single IP), on port 5060 with the label “TestServer”,
The second one we added to group 200 was a subnet of 192.168.1.0 with a /24 subnet mask (255 IPs), on port 5060 with the label “OfficeSubnet”
On startup, or when we manually reload the addressTable, Kamailio grabs all the records and stores them in RAM. This makes lookup super fast, but the tradeoff is you have to load the entries, so changes aren’t immediate.
Let’s use Kamcmd to reload the entries and check their status.
kamcmd permissions.addressReload
kamcmd permissions.addressDump
kamcmd permissions.subnetDump
You should see the single IP in the output of the permissions.addressDump and see the subnet on the subnetDump:
Usage
It’s usage is pretty simple, combined with a simple nested if statement.
if (allow_source_address("200")) {
xlog("Coming from address group 200");
};
if (allow_source_address("250")) {
xlog("Coming from address group 250");
};
The above example just outputs to xlog with the address group, but we can expand upon this to give us our ACL service.
if (allow_source_address("200")) {
xlog("Coming from address group 200");
}else if (allow_source_address("250")) {
xlog("Coming from address group 250");
}else{
sl_reply("401", "Address not authorised");
exit;
}
If we put this at the top of our Kamailio config we’ll reply with a 401 response to any traffic not in address group 200 or 250.
So by now we’ve secured our box and we’re able to route calls between registered endpoints.
Next up we’ll need to add some external connectivity, meaning we can reach destinations that aren’t directly registered on our Kamailio instance.
We’ve signed up with imaginary carrier at “imaginarycarrier.com” so we can make / receive calls from the PSTN using them as a trunk. They’ll be authenticating us based on our Source IP which we’ve let them know.
At the moment, when we receive an INVITE where the destination isn’t registered, we respond with a 404:
sl_reply("404", "User not Registered"); #If looking up location fails reply with 404
But now we’ve got a carrier we can send calls to if the destination isn’t on our PBX, so we won’t need to reply 404 anymore for calls from our users.
So let’s only give the 404 reply to calls in from our carrier (inbound calls), and instead of giving a 404 response to callers from within our organisation, let’s send the calls to the carrier to make an outbound call.
This means calls to any destination that isn’t registered on Kamailio will go to the Carrier.
But we’ll need to still respond with the 404 response if a carrier sends us a call to a destination that isn’t registered, like an inbound call to a user who isn’t registered.
route[ONNETINVITE]{
if(!lookup("location")){ #Try looking up location
#If looking up location fails then:
if(allow_source_address("200")){ #Check if the request has come from a carrier
sl_reply("404", "User not Registered"); #If if it is from a carrier to a destination we don't have registered reply with 404
exit; #And exit
}else{ #If it's not from a carrier
route(TOCARRIER); #Route the call out to the carrier (to make an external call)
}
}
route(RELAY); #Relay traffic to endpoint
exit(); #Exit
}
Next we’ll need to create our TOCARRIER route,
route[TOCARRIER]{ #Route to send calls to a carrier at 192.168.200.130
rewritehost("imaginarycarrier.com"); #Rewrite host to be the carrier's IP
route(RELAY); #Route relay
}
So let’s put this together and try and make an outbound call.
Call Flow
Outbound call to Carrier
First we see our UA make the call leg to Kamailio
UA to Kamailio: SIP: INVITE sip:61299999999@kamailio SIP/2.0
Kamailio asks the UA to authenticate itself and send that again, the UA does:
It’s not in address group 200, as the from address isn’t one of our Carrier’s IPs, so it ends up at
route(TOCARRIER);
The route block itself rewrites the host part of the request to be the Carrier’s IP, and then forwards it on to the carrier.
route[TOCARRIER]{ #Route to send calls to a carrier at 192.168.200.130
rewritehost("imaginarycarrier.com"); #Rewrite host to be the carrier's IP
route(RELAY); #Route relay
}
So now the INVITE has been forwarded to imaginarycarrier.com, and because we called route(RELAY); it’ll handle all the in dialog requests.
Inbound call from Carrier
So now we know how an outbound call flows, let’s look at inbound calls from the carrier.
Carrier to Kamailio: INVITE sip:61312341234@kamailio SIP/2.0
Kamailio responds with a provisional response of 100 Trying
Kamailio to Carrier: SIP: SIP/2.0 100 trying -- your call is important to us
Now Kamailio checks to see the method type – It’s INVITE, and if the source address is in Address Group 200 (As we defined in Part 8), it is, so it calls the route(ONNETINVITE) block.
if(method=="INVITE"){
if(allow_source_address("200")){ #If from a Carrier IP
route(ONNETINVITE); #Call INVITE handling bloc
Once we get to the ONNETINVITE block Kamailio tries to lookup the location, to see if there’s a device registered with a username that matches 61312341234, using:
lookup("location")
There is, so the route(relay) is called, which forwards the INVITE to the IP it has an Address on Record for for 61312341234.
The carrier sends the INVITE to Kamailio, Kamailio calls lookup location, which fails to return a location as 61299999999 isn’t registered.
Next we check to see if the call is from a carrier by checking if the source address of the INVITE is equal to address group 200.
if(allow_source_address("200")){ #Check if the request has come from a carrier
sl_reply("404", "User not Registered"); #If if it is from a carrier to a destination we don't have registered reply with 404
exit;
As the source address is in address group 200, the carrier gets the 404 “User not Registered” reply, as we see in the packet capture:
Kamailio > Carrier: SIP: SIP/2.0 404 User not Registered
SIP was written to be fast and resonably lightweight.
At the time SIP was created in 1996, Motorola just had launched it’s first flip phone, the web was only 100,000 websites online and I was playing Pokémon.
Security wasn’t so much an afterthought, but rather not something everyone was as conscious of as they are today.
UDP is the protocol of choice for most SIP deployments, which opens it up for Message Amplification attacks.
As the world saw a few years back with DNS Amplification attacks (Good explanation of how Message Amplification works courtesy of Cloudflare), amplification attacks are enabled by DNS requests being smaller than DNS responses, and carrier networks that don’t verify the source of their traffic allowing someone to request a DNS lookup saying they’re from an IP that isn’t theirs, and that IP getting flooded.
SIP is vulnerable to this too, not exactly zero-day exploits, but something that hasn’t been looked at outside of the theoretical sphere, so I thought I’d roll up my sleeves and see how bad it can look.
For starters it’s worth remembering for a Message Amplification attack to work, it’s got to amplify. RF Engineering will teach you that amplification is the ratio of power in to power out, and it’s the same for Message Amplification, the size of the packet we send has to be smaller than the packet received, else we’re just using someone else to do our dirty work, but not amplifying.
Typical Response Sizes
These are small SIP messages I created in Python using sockets, they’re not the absolute smallest you could go, but they were as small as I could go and still get through the basic packet validation / sanity checks.
Some SIP Proxies drop traffic missing required headers while some don’t, I’ve included the required headers.
I’ve pointed the traffic at a Kamailio instance and measured the bytes sent vs bytes returned.
Method
Request Size (bytes)
Response Size (bytes)
Gain
OPTIONS
168
209
1.2x
REGISTER
380
411
1x
INVITE
197
377
1.9x
Content Length Mismatch
339
400 Missing Required Header in Request
300
Max Forwards Exceeded
213
So the best we can get is a packet 1.9 times the size of the packet we put in, which means SIP isn’t the best for Message Amplification attacks, but passable, so long as it keeps responding.
INVITE gets our best amplification and we can tune this to get the request smaller.
The Workhorse
Carrier grade SIP servers are pretty powerful machines, able to handle huge amounts of traffic, quite literally hundreds of millions a day, generally split across geographic areas and clustered, all on high quality low loss, low latency IP links.
If you have 20,000 subscribers sending a keep alive every 60 seconds, you’re at 72 million dialogs consisting of two packets each (144,000,000 SIP messages).
So after some stripping down I managed to get a valid INVITE that would be responded too with an auth challenge (407 Proxy Authentication Required) which was 125 bytes on the wire, while the response was 330, giving me a gain of 2.64 times what I put in. (I send 125 bytes, I get back 330)
The Setup
We’ve got 3 IPs we’re dealing with here,
Our victim is on 10.0.1.15. UDP port 5060 won’t even be open for this poor fellow, but he’ll get flooded.
Next is our attacker who’s machine is on 10.0.1.12, but claiming their source IP is 10.0.1.15 (the Victim’s IP)
From here our attacker will be sending SIP traffic to 10.0.1.110 (our “carrier” / SIP server), which will send it’s responses to the victim. I’ve spun up an Asterisk instance because it’s the voice eng version of sticky tape, I’d love to test this against something a Broadsoft platform, but licences are hard to come by.
I setup the Asterisk instance to be single threaded, on a box with just enough resources to run, to try this small scale.
I wrote a threaded Python script that will ramp up the number of messages exponentially, we’ll start by sending one message per second, then two messages per second, and so on.
And we’ll do this until something breaks.
The Results
In short – inconclusive at first, but kinda scary after that.
Asterisk died really quickly. “Exceptionally long queue length” popped up after the first second. Interestingly, the box eventually came good and actually replied to every one of our requests, and even sent a BYE. Cute.
So I modified the script to be a bit less aggressive, a random wait time between 0 and 1 seconds between loops for each thread.
I got about 60 seconds in before Asterisk really stopped responding to traffic.
So I tweaked my script again, enabled multi threading on the “carrier” and tried again.
So here’s the best rate (packets per second) I could get after a lot of tweaking:
Peak Receive: 14,000 packets per second Peak Send: 20,000 packets per second
In terms of packet size – what we really care about, the results were actually pretty promising:
Peak receive rate of 67Mbps, for which we were putting in ~25Mbps.
So can SIP be used for message amplification attacks? Sure.
Is it particulary practical? Not really. There’s easier targets out there for the time being, so VoIP will be spared the worst of it.
But for a carrier weaponisation of carrier SIP server should be a real fear.
Protection for Carriers
Don’t use UDP for your SIP traffic.
It’s easier said than done, I know… But the reasoning for putting SIP on UDP was primarily speed and limited bandwidth, but with more and more fibre in the ground it’s no longer the case.
SIP over TCP (better yet use it as an excuse to move to TLS), will protect you from some of these attacks.
Flood protection is built into most SBCs these days, if your box is being used to hit a specific target, the source IP will be masquerading as the target. So blocking that and not responding is your best bet. Lots of SBCs still respond with a 4xx “Rate Limiting” response instead of just dropping the traffic, ideally you’d disable the nice “Rate liming” response and just drop the traffic.
Traffic modeling, GeoIP blocking and rate limiting per IP & destination port will also help, as well as monitoring.
Ultimately you can’t stop spoofed UDP traffic coming into your network, but you can stop UDP traffic leaving your network, and if everyone did that we wouldn’t be in this mess.
UDP spoofing is made possible by networks that don’t verify that the traffic that’s leaving their network is traffic that is sourced from your network.
Your core routers know what IPs are assigned to your network, and should be configured to drop traffic that’s leaving the network but not coming from those IPs.
IETF came up with this solution, and it’s built into all major router OSes: