Popular Posts

Saturday, November 6, 2010

Distributed VoIP Scanning Mitigation for FreeSWITCH

There has been some concern recently about distributed SIP scanning taking place by botnets for the purpose of toll fraud. Many devices may not have a solution for this, but the Spitfire application I have been writing will allow FreeSWITCH users, at least, to mitigate these attacks. As previously mentioned in my post about Taming SIPVicious, I wrote a patch for FreeSWITCH which adds the sofia::pre_register event.

diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.h b/src/mod/endpoints/mod_sofia/mod_sofia.h
index 90f5d3a..06bf36c 100644
--- a/src/mod/endpoints/mod_sofia/mod_sofia.h
+++ b/src/mod/endpoints/mod_sofia/mod_sofia.h
@@ -76,6 +76,8 @@ typedef struct private_object private_object_t;

 #define SOFIA_SESSION_TIMEOUT "sofia_session_timeout"
 #define MY_EVENT_REGISTER "sofia::register"
+#define MY_EVENT_PRE_REGISTER "sofia::pre_register"
+#define MY_EVENT_REGISTER_ATTEMPT "sofia::register_attempt"
 #define MY_EVENT_UNREGISTER "sofia::unregister"
 #define MY_EVENT_EXPIRE "sofia::expire"
 #define MY_EVENT_GATEWAY_STATE "sofia::gateway_state"
diff --git a/src/mod/endpoints/mod_sofia/sofia_reg.c b/src/mod/endpoints/mod_sofia/sofia_reg.c
index 51ce56f..e8fdcd2 100644
--- a/src/mod/endpoints/mod_sofia/sofia_reg.c
+++ b/src/mod/endpoints/mod_sofia/sofia_reg.c
@@ -993,11 +993,37 @@ uint8_t sofia_reg_handle_register(nua_t *nua, sofia_profile_t *profile, nua_hand

        if (authorization) {
                char *v_contact_str;
+               const char *username = "unknown";
+               const char *realm = reg_host;
                if ((auth_res = sofia_reg_parse_auth(profile, authorization, sip, sip->sip_request->rq_method_name,
                                 key, keylen, network_ip, v_event, exptime, regtype, to_user, &auth_params, &reg_count)) == AUTH_STALE) {
                        stale = 1;
                }

+
+               if (auth_params) {
+                       username = switch_event_get_header(auth_params, "sip_auth_username");
+                       realm = switch_event_get_header(auth_params, "sip_auth_realm");
+               }
+               if (switch_event_create_subclass(&s_event, SWITCH_EVENT_CUSTOM, MY_EVENT_REGISTER_ATTEMPT) == SWITCH_STATUS_SUCCESS) {
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "profile-name", profile->name);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "from-user", to_user);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "from-host", reg_host);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "contact", contact_str);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "call-id", call_id);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "rpid", rpid);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "status", reg_desc);
+                       switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "expires", "%ld", (long) exptime);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "to-user", from_user);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "to-host", from_host);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "network-ip", network_ip);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "network-port", network_port_c);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "username", username);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "realm", realm);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "user-agent", agent);
+                       switch_event_fire(&s_event);
+               }
+
                if (exptime && v_event && *v_event) {
                        char *exp_var;
                        char *allow_multireg = NULL;
@@ -1101,6 +1127,23 @@ uint8_t sofia_reg_handle_register(nua_t *nua, sofia_profile_t *profile, nua_hand
        if (!authorization || stale) {
                const char *realm = profile->challenge_realm;

+               if (switch_event_create_subclass(&s_event, SWITCH_EVENT_CUSTOM, MY_EVENT_PRE_REGISTER) == SWITCH_STATUS_SUCCESS) {
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "profile-name", profile->name);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "from-user", to_user);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "from-host", reg_host);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "contact", contact_str);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "call-id", call_id);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "rpid", rpid);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "status", reg_desc);
+                       switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "expires", "%ld", (long) exptime);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "to-user", from_user);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "to-host", from_host);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "network-ip", network_ip);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "network-port", network_port_c);
+                       switch_event_add_header_string(s_event, SWITCH_STACK_BOTTOM, "user-agent", agent);
+                       switch_event_fire(&s_event);
+               }
+
                if (zstr(realm) || !strcasecmp(realm, "auto_to")) {
                        realm = to_host;
                } else if (!strcasecmp(realm, "auto_from")) {


That patch also includes an additional event which was intended to be used for mitigating distributed SIP scanning, when and if it ever started happening. It appears that within the past few weeks, that has become a reality.

The additional event added is sofia::register_attempt. This event is fired when a REGISTER is received by FreeSWITCH which contains credentials. During a successful registration, three events will fire. First, sofia::pre_register will fire when the first REGISTER (without credentials) is received. Then, when credentials are sent during the second REGISTER request, sofia::register_attempt will fire. If the credentials are correct, then sofia::register will fire. By keying in on these three events, it is possible to identify whether the person sending the REGISTER requests is scanning or attempting to log in. Further, you can count failed requests and decide to firewall the IP.

I will provide a configuration for Spitfire when it sells which contains the logic required to detect and block such scans. Basically, the way it will work is this:

  1. Start a timer which runs every minute that:
    1. Decrements each source IP request counter by 14.
    2. Dumps all IPs that still have a request count higher than 14 and firewalls them.
  2. When sofia::pre_register comes in, increment the request counter for the source IP.
  3. When sofia::register_attempt comes in:
    1. Increment request counter for the source IP.
    2. Increment attempt counter for the source IP.
    3. If attempt counter exceeds 3, firewall the IP.
  4. When sofia::register event comes in, clear the attempt counter and clear the request counter.
Obviously, there are limits to what you can detect like this. A normal registration attempt requires two REGISTER requests. Limiting a single source IP to 14 requests in a one-minute interval allows up to 7 full registration attempts in one minute. Normal users should never exceed this. This will detect people scanning an IP without making password attempts. When an IP starts sending password guessing attempts, that will be detected with the attempt counter. If the registration succeeds it is probably a legitimate user, so the attempt and request counters are cleared. Thus, if we ever hit an attempt count of 4, it means it failed 3 times already. This might occur for a legitimate user if their account is just being set up. But, once setup, you should never have to worry about that again.

By using a sufficiently long password (12 or more characters) which is randomly generated and contains upper and lower case letters, numbers, and symbols, the chances of guessing such a password within the lifetime of the password's use (even if it is used for years) is so small it can be ignored. There are literally 12^94 possible combinations which is 2.77 * 10^101 total combinations. That is 27.7 google combinations. Note that this number is so large that it completely dwarfs the estimated number of atoms in the entire universe. If the attacker were somehow able to test 1,000,000 combinations per second, the universe would die of entropy or a big crunch long before the attack finished. The bottom line is that you can randomly generate such a password, put it in the configuration once, and then not worry about the user being firewalled at some point in the future because they need to change their password.

So, for those of you running FreeSWITCH who need to mitigate this issue, contact Anthony Minessale II (the author of FreeSWITCH) through FreeSWITCH Solutions and let him know you are interested in Spitfire when it is released.

No comments:

Post a Comment