md5 hashed passwords in asterisk sip.conf register => statements

Dana Harding, mail address - 2009-06-08

Asterisk is a registered trademark of Digium - see www.digum.com for more information.


Introduction

I was recently exploring some of openssl's various encryption and hashing abilities, and turned my focus onto how my Asterisk server handles authentication. Asterisk uses a digest access method for authentication.
I found it interesting that sip.conf allowed for a md5secret option for peers and users, but it was not allowed for the general register=> statements in the [general] context.

Being a very amateur C programmer - I decided to look at the code to see why this was. It turns out all the hard work is already done, and it only takes minor changes to make this happen.

sip.conf

old:register => user[:secret[:authuser]]@host[:port][/contact]
example:register => myacct:mypassword@myitsp.com
new:register => user[<:secret|:#md5secret#>[:authuser]]@host[:port]
example:register => myacct:#ed343062ddf0c2060e5e60f43eb64883#@myitsp.com

Calculating the Hash:

The hash is the MD5 hash of the string "<username>:<realm>:<password>"

Username and password are pretty clear cut. For local channels, the realm by default is "asterisk". For ITSPs - you will need to observe some SIP packets to determine the proper realm. In the example above, I use the realm "myitsp.com".

user@machine:~$ echo -n "myacct:myitsp.com:mypassword" | openssl dgst -md5
ed343062ddf0c2060e5e60f43eb64883
user@machine:~$



Security

This actually isn't very much more secure than the plaintext passwords in the configuration file, and is probably why the main source has not been changed.
Because of the way the digest authentication works, for the purposes of making SIP calls: A STOLEN HASH IS EQUIVALENT TO A STOLEN PASSWORD. The cracker who has obtained a copy of this hash can authenticate as you, and can register as you to send and receive your VoIP calls.

So why bother?

Asterisk 1.4.23.1
server*CLI> sip show users
Username Secret AccountcodeDef.Context ACLNAT
spa3000 spapassword internal YesAlways
test internal YesAlways
home homepassword internal YesAlways
myitsp-abc itsp_password73 from-itsp NoRFC3581
server*CLI>

The SIP user "test" uses md5secret instead of the plaintext secret - note no hash displayed.

You really shouldn't be giving anybody access to your CLI or manager interface unless they are already supposed to know all the passwords, but it does illustrate one vector of attack. How do you know the http based manager interface you just installed for users to have a better viewport and control over their PBX doesn't have an injection vulnerability?



The Meat



Excerpt from channels/chan_sip.c distributed with Asterisk 1.4.23.1
Modifications colourized

/* * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 1999 - 2006, Digium, Inc.
 *
 * Mark Spencer 
 *
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2. See the LICENSE file
 * at the top of the source tree.
 */

<- snip ->

/*! \brief Parse register=> line in sip.conf and add to registry */
static int sip_register(char *value, int lineno)
{
        struct sip_registry *reg;
        int portnum = 0;
        char username[256] = "";
        char *hostname=NULL, *secret=NULL, *authuser=NULL;
        char *md5secret=NULL;
        char *porta=NULL;
        char *contact=NULL;

        if (!value)
                return -1;
        ast_copy_string(username, value, sizeof(username));
        /* First split around the last '@' then parse the two components. */
        hostname = strrchr(username, '@'); /* allow @ in the first part */
        if (hostname)
                *hostname++ = '\0';
        if (ast_strlen_zero(username) || ast_strlen_zero(hostname)) {
                /* should modify this and other builtin documentation to show the new format */
                ast_log(LOG_WARNING, "Format for registration is user[:secret[:authuser]]@host[:port][/contact] at line %d\n", lineno);
                return -1;
        }
        /* split user[:secret[:authuser]] */
        secret = strchr(username, ':');
        if (secret) {
                *secret++ = '\0';
                authuser = strchr(secret, ':');
                if (authuser)
                        *authuser++ = '\0';
                /* special case - 34-byte long password strings with #'s as the first and last characters              */
                /* strip the #'s and assign the remainder to be the md5secret (md5 is 32 bytes when written out in hex)*/
                if ((*secret == '#') && (*(secret + strlen(secret)-1) == '#') && (strlen(secret) == 34)) {
                        *(secret + strlen(secret) - 1) = '\0';
                        md5secret=(++secret);
                        secret=NULL;
                }

        }
        /* split host[:port][/contact] */
        contact = strchr(hostname, '/');
        if (contact)
                *contact++ = '\0';
        if (ast_strlen_zero(contact))
                contact = "s";
        porta = strchr(hostname, ':');
        if (porta) {
                *porta++ = '\0';
                portnum = atoi(porta);
                if (portnum == 0) {
                        ast_log(LOG_WARNING, "%s is not a valid port number at line %d\n", porta, lineno);
                        return -1;
                }
        }
        if (!(reg = ast_calloc(1, sizeof(*reg)))) {
                ast_log(LOG_ERROR, "Out of memory. Can't allocate SIP registry entry\n");
                return -1;
        }

        if (ast_string_field_init(reg, 256)) {
                ast_log(LOG_ERROR, "Out of memory. Can't allocate SIP registry strings\n");
                free(reg);
                return -1;
        }

        regobjs++;
        ASTOBJ_INIT(reg);
        ast_string_field_set(reg, contact, contact);
        if (!ast_strlen_zero(username))
                ast_string_field_set(reg, username, username);
        if (hostname)
                ast_string_field_set(reg, hostname, hostname);
        if (authuser)
                ast_string_field_set(reg, authuser, authuser);
        if (secret)
                ast_string_field_set(reg, secret, secret);
        if (md5secret)
                ast_string_field_set(reg, md5secret, md5secret);
        reg->expire = -1;
        reg->timeout =  -1;
        reg->refresh = default_expiry;
        reg->portno = portnum;
        reg->callid_valid = FALSE;
        reg->ocseq = INITIAL_CSEQ;
        ASTOBJ_CONTAINER_LINK(®l, reg);      /* Add the new registry entry to the list */
        ASTOBJ_UNREF(reg,sip_registry_destroy);
        return 0;
}

<- snip ->