summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerikdejong <erikdejong@gmail.com>2017-02-28 20:24:41 +0100
committerMichael Mann <mmann78@netscape.net>2017-03-03 19:28:57 +0000
commitbc94ffcb0c754591e3dbac368f90e7f91d4a64bb (patch)
treec083c238d47abec1d598633a6e569e9fae089e24
parentca68749606bb78a333a0961dbf9fa74c65fa72aa (diff)
downloadwireshark-bc94ffcb0c754591e3dbac368f90e7f91d4a64bb.tar.gz
SIP: Add digest verification functionality
Added functionality to verify SIP authorization lines. With this functionality it's possible to find faulty passwords that were added to configuration by automatic processes (eg having unescaped '&' characters in XML config files) resulting in authorization failures that cannot be diagnosed otherwise. Other uses include bug hunting in SIP stacks. Bug: 13444 Change-Id: I5abecd048480c8f5130a5112c531587c5993f12f Reviewed-on: https://code.wireshark.org/review/20314 Petri-Dish: Michael Mann <mmann78@netscape.net> Reviewed-by: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Michael Mann <mmann78@netscape.net>
-rw-r--r--epan/dissectors/packet-sip.c263
1 files changed, 254 insertions, 9 deletions
diff --git a/epan/dissectors/packet-sip.c b/epan/dissectors/packet-sip.c
index eca88dcb22..db4406310d 100644
--- a/epan/dissectors/packet-sip.c
+++ b/epan/dissectors/packet-sip.c
@@ -44,9 +44,12 @@
#include <epan/tap.h>
#include <epan/proto_data.h>
#include <epan/uat.h>
+#include <epan/strutil.h>
+#include <epan/to_str.h>
#include <wsutil/str_util.h>
#include <wsutil/strtoi.h>
+#include <wsutil/wsgcrypt.h>
#include "packet-ssl.h"
@@ -272,6 +275,7 @@ static expert_field ei_sip_sipsec_malformed = EI_INIT;
static expert_field ei_sip_via_sent_by_port = EI_INIT;
static expert_field ei_sip_content_length_invalid = EI_INIT;
static expert_field ei_sip_Status_Code_invalid = EI_INIT;
+static expert_field ei_sip_authorization_invalid = EI_INIT;
/* patterns used for tvb_ws_mempbrk_pattern_guint8 */
static ws_mempbrk_pattern pbrk_comma_semi;
@@ -968,12 +972,8 @@ header_fields_free_cb(void*r)
{
header_field_t* rec = (header_field_t*)r;
- if (rec->header_name) {
- g_free(rec->header_name);
- }
- if (rec->header_desc) {
- g_free(rec->header_desc);
- }
+ g_free(rec->header_name);
+ g_free(rec->header_desc);
}
static void
@@ -1028,6 +1028,89 @@ header_fields_initialize_cb(void)
UAT_CSTRING_CB_DEF(sip_custom_header_fields, header_name, header_field_t)
UAT_CSTRING_CB_DEF(sip_custom_header_fields, header_desc, header_field_t)
+/* SIP authorization parameters */
+static gboolean global_sip_validate_authorization = FALSE;
+
+typedef struct _authorization_user_t {
+ gchar* username;
+ gchar* realm;
+ gchar* password;
+} authorization_user_t;
+
+static authorization_user_t* sip_authorization_users = NULL;
+static guint sip_authorization_num_users = 0;
+
+static gboolean
+authorization_users_update_cb(void *r, char **err)
+{
+ authorization_user_t *rec = (authorization_user_t *)r;
+ char c;
+
+ if (rec->username == NULL) {
+ *err = g_strdup("Username can't be empty");
+ return FALSE;
+ }
+
+ g_strstrip(rec->username);
+ if (rec->username[0] == 0) {
+ *err = g_strdup("Username can't be empty");
+ return FALSE;
+ }
+
+ /* Check for invalid characters (to avoid asserting out when
+ * registering the field).
+ */
+ c = proto_check_field_name(rec->username);
+ if (c) {
+ *err = g_strdup_printf("Username can't contain '%c'", c);
+ return FALSE;
+ }
+
+ *err = NULL;
+ return TRUE;
+}
+
+static void *
+authorization_users_copy_cb(void* n, const void* o, size_t siz _U_)
+{
+ authorization_user_t* new_rec = (authorization_user_t*)n;
+ const authorization_user_t* old_rec = (const authorization_user_t*)o;
+
+ if (old_rec->username) {
+ new_rec->username = g_strdup(old_rec->username);
+ } else {
+ new_rec->username = NULL;
+ }
+
+ if (old_rec->realm) {
+ new_rec->realm = g_strdup(old_rec->realm);
+ } else {
+ new_rec->realm = NULL;
+ }
+
+ if (old_rec->password) {
+ new_rec->password = g_strdup(old_rec->password);
+ } else {
+ new_rec->password = NULL;
+ }
+
+ return new_rec;
+}
+
+static void
+authorization_users_free_cb(void*r)
+{
+ authorization_user_t* rec = (authorization_user_t*)r;
+
+ g_free(rec->username);
+ g_free(rec->realm);
+ g_free(rec->password);
+}
+
+UAT_CSTRING_CB_DEF(sip_authorization_users, username, authorization_user_t)
+UAT_CSTRING_CB_DEF(sip_authorization_users, realm, authorization_user_t)
+UAT_CSTRING_CB_DEF(sip_authorization_users, password, authorization_user_t)
+
/* Forward declaration we need below */
void proto_reg_handoff_sip(void);
static gboolean dissect_sip_common(tvbuff_t *tvb, int offset, int remaining_length, packet_info *pinfo,
@@ -1059,6 +1142,35 @@ static guint sip_find_invite(packet_info *pinfo,
guchar cseq_number_set, guint32 cseq_number,
guint32 *response_time);
+typedef struct
+{
+ gchar * username;
+ gchar * realm;
+ gchar * uri;
+ gchar * nonce;
+ gchar * cnonce;
+ gchar * nonce_count;
+ gchar * response;
+ gchar * qop;
+ gchar * algorithm;
+ gchar * method;
+} sip_authorization_t;
+
+static authorization_user_t * sip_get_authorization(sip_authorization_t *authorization_info);
+static gboolean sip_validate_authorization(sip_authorization_t *authorization_info, gchar *password);
+
+static authorization_user_t * sip_get_authorization(sip_authorization_t *authorization_info)
+{
+ guint i;
+ for (i = 0; i < sip_authorization_num_users; i++) {
+ if ((!strcmp(sip_authorization_users[i].username, authorization_info->username)) &&
+ (!strcmp(sip_authorization_users[i].realm, authorization_info->realm))) {
+ return &sip_authorization_users[i];
+ }
+ }
+ return NULL;
+}
+
/* SIP content type and internet media type used by other dissectors
* are the same. List of media types from IANA at:
* http://www.iana.org/assignments/media-types/index.html */
@@ -1946,7 +2058,7 @@ dissect_sip_contact_item(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gi
* Returns offset at end of parsing, or -1 for unsuccessful parsing
*/
static gint
-dissect_sip_authorization_item(tvbuff_t *tvb, proto_tree *tree, gint start_offset, gint line_end_offset)
+dissect_sip_authorization_item(tvbuff_t *tvb, proto_tree *tree, gint start_offset, gint line_end_offset, sip_authorization_t *authorization_info)
{
gint current_offset, par_name_end_offset, queried_offset, value_offset, value_search_offset;
gint equals_offset = 0;
@@ -2011,6 +2123,33 @@ dissect_sip_authorization_item(tvbuff_t *tvb, proto_tree *tree, gint start_offse
proto_tree_add_item(tree, *(auth_parameter->hf_item), tvb,
value_offset, current_offset - value_offset,
ENC_UTF_8|ENC_NA);
+ if (global_sip_validate_authorization) {
+ gint real_value_offset = value_offset;
+ gint real_value_length = current_offset - value_offset;
+ if ((tvb_get_guint8(tvb, value_offset) == '\"') && (tvb_get_guint8(tvb, current_offset - 1) == '\"') && (real_value_length > 1)) {
+ real_value_offset++;
+ real_value_length -= 2;
+ }
+ if (g_ascii_strcasecmp(name, "response") == 0) {
+ authorization_info->response = tvb_get_string_enc(wmem_packet_scope(), tvb, real_value_offset, real_value_length, ENC_ASCII);
+ } else if (g_ascii_strcasecmp(name, "nc") == 0) {
+ authorization_info->nonce_count = tvb_get_string_enc(wmem_packet_scope(), tvb, real_value_offset, real_value_length, ENC_ASCII);
+ } else if (g_ascii_strcasecmp(name, "username") == 0) {
+ authorization_info->username = tvb_get_string_enc(wmem_packet_scope(), tvb, real_value_offset, real_value_length, ENC_ASCII);
+ } else if (g_ascii_strcasecmp(name, "realm") == 0) {
+ authorization_info->realm = tvb_get_string_enc(wmem_packet_scope(), tvb, real_value_offset, real_value_length, ENC_ASCII);
+ } else if (g_ascii_strcasecmp(name, "algorithm") == 0) {
+ authorization_info->algorithm = tvb_get_string_enc(wmem_packet_scope(), tvb, real_value_offset, real_value_length, ENC_ASCII);
+ } else if (g_ascii_strcasecmp(name, "nonce") == 0) {
+ authorization_info->nonce = tvb_get_string_enc(wmem_packet_scope(), tvb, real_value_offset, real_value_length, ENC_ASCII);
+ } else if (g_ascii_strcasecmp(name, "qop") == 0) {
+ authorization_info->qop = tvb_get_string_enc(wmem_packet_scope(), tvb, real_value_offset, real_value_length, ENC_ASCII);
+ } else if (g_ascii_strcasecmp(name, "cnonce") == 0) {
+ authorization_info->cnonce = tvb_get_string_enc(wmem_packet_scope(), tvb, real_value_offset, real_value_length, ENC_ASCII);
+ } else if (g_ascii_strcasecmp(name, "uri") == 0) {
+ authorization_info->uri = tvb_get_string_enc(wmem_packet_scope(), tvb, real_value_offset, real_value_length, ENC_ASCII);
+ }
+ }
break;
}
}
@@ -3882,6 +4021,8 @@ dissect_sip_common(tvbuff_t *tvb, int offset, int remaining_length, packet_info
/* Add tree using whole text of line */
if (hdr_tree) {
proto_item *ti_c;
+ sip_authorization_t authorization_info = { 0 };
+ authorization_user_t * authorization_user = NULL;
/* Add whole line as header tree */
sip_element_item = sip_proto_tree_add_string(hdr_tree,
hf_header_array[hf_index], tvb,
@@ -3912,7 +4053,7 @@ dissect_sip_common(tvbuff_t *tvb, int offset, int remaining_length, packet_info
}
/* Parse each individual parameter in the line */
- while ((comma_offset = dissect_sip_authorization_item(tvb, sip_element_tree, comma_offset, line_end_offset)) != -1)
+ while ((comma_offset = dissect_sip_authorization_item(tvb, sip_element_tree, comma_offset, line_end_offset, &authorization_info)) != -1)
{
if(comma_offset == line_end_offset)
{
@@ -3927,6 +4068,15 @@ dissect_sip_common(tvbuff_t *tvb, int offset, int remaining_length, packet_info
}
comma_offset++; /* skip comma */
}
+ if ((authorization_info.response != NULL) && (global_sip_validate_authorization)) { /* If there is a response, check for valid credentials */
+ authorization_user = sip_get_authorization(&authorization_info);
+ if (authorization_user) {
+ authorization_info.method = wmem_strdup(wmem_packet_scope(), stat_info->request_method);
+ if (!sip_validate_authorization(&authorization_info, authorization_user->password)) {
+ proto_tree_add_expert_format(tree, pinfo, &ei_sip_authorization_invalid, tvb, offset, line_end_offset - offset, "SIP digest does not match known password %s", authorization_user->password);
+ }
+ }
+ }
}/*hdr_tree*/
break;
@@ -5136,6 +5286,65 @@ guint sip_find_invite(packet_info *pinfo,
return result;
}
+static gboolean sip_validate_authorization(sip_authorization_t *authorization_info, gchar *password) {
+ gchar ha1[33] = {0};
+ gchar ha2[33] = {0};
+ gchar response[33] = {0};
+ gcry_md_hd_t md5_handle;
+ if ( (authorization_info->qop == NULL) ||
+ (authorization_info->username == NULL) ||
+ (authorization_info->realm == NULL) ||
+ (authorization_info->method == NULL) ||
+ (authorization_info->uri == NULL) ||
+ (authorization_info->nonce == NULL) ) {
+ return TRUE; /* If no qop, discard */
+ }
+ if (strcmp(authorization_info->qop, "auth") ||
+ (authorization_info->nonce_count == NULL) ||
+ (authorization_info->cnonce == NULL) ||
+ (authorization_info->response == NULL) ||
+ (password == NULL)) {
+ return TRUE; /* Obsolete or not enough information, discard */
+ }
+
+ if (gcry_md_open(&md5_handle, GCRY_MD_MD5, 0)) {
+ return FALSE;
+ }
+
+ gcry_md_write(md5_handle, authorization_info->username, strlen(authorization_info->username));
+ gcry_md_putc(md5_handle, ':');
+ gcry_md_write(md5_handle, authorization_info->realm, strlen(authorization_info->realm));
+ gcry_md_putc(md5_handle, ':');
+ gcry_md_write(md5_handle, password, strlen(password));
+ /* Array is zeroed out so there is always a \0 at index 32 for string termination */
+ bytes_to_hexstr(ha1, gcry_md_read(md5_handle, 0), HASH_MD5_LENGTH);
+ gcry_md_reset(md5_handle);
+ gcry_md_write(md5_handle, authorization_info->method, strlen(authorization_info->method));
+ gcry_md_putc(md5_handle, ':');
+ gcry_md_write(md5_handle, authorization_info->uri, strlen(authorization_info->uri));
+ /* Array is zeroed out so there is always a \0 at index 32 for string termination */
+ bytes_to_hexstr(ha2, gcry_md_read(md5_handle, 0), HASH_MD5_LENGTH);
+ gcry_md_reset(md5_handle);
+ gcry_md_write(md5_handle, ha1, strlen(ha1));
+ gcry_md_putc(md5_handle, ':');
+ gcry_md_write(md5_handle, authorization_info->nonce, strlen(authorization_info->nonce));
+ gcry_md_putc(md5_handle, ':');
+ gcry_md_write(md5_handle, authorization_info->nonce_count, strlen(authorization_info->nonce_count));
+ gcry_md_putc(md5_handle, ':');
+ gcry_md_write(md5_handle, authorization_info->cnonce, strlen(authorization_info->cnonce));
+ gcry_md_putc(md5_handle, ':');
+ gcry_md_write(md5_handle, authorization_info->qop, strlen(authorization_info->qop));
+ gcry_md_putc(md5_handle, ':');
+ gcry_md_write(md5_handle, ha2, strlen(ha2));
+ /* Array is zeroed out so there is always a \0 at index 32 for string termination */
+ bytes_to_hexstr(response, gcry_md_read(md5_handle, 0), HASH_MD5_LENGTH);
+ gcry_md_close(md5_handle);
+ if (!strncmp(response, authorization_info->response, 32)) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
/* TAP STAT INFO */
/*
@@ -6762,12 +6971,14 @@ void proto_register_sip(void)
{ &ei_sip_sipsec_malformed, { "sip.sec_mechanism.malformed", PI_MALFORMED, PI_WARN, "SIP Security-mechanism header malformed", EXPFILL }},
{ &ei_sip_via_sent_by_port, { "sip.Via.sent-by.port.invalid", PI_MALFORMED, PI_NOTE, "Invalid SIP Via sent-by-port", EXPFILL }},
{ &ei_sip_content_length_invalid, { "sip.content_length.invalid", PI_MALFORMED, PI_NOTE, "Invalid content_length", EXPFILL }},
- { &ei_sip_Status_Code_invalid, { "sip.Status-Code.invalid", PI_MALFORMED, PI_NOTE, "Invalid Status-Code", EXPFILL }}
+ { &ei_sip_Status_Code_invalid, { "sip.Status-Code.invalid", PI_MALFORMED, PI_NOTE, "Invalid Status-Code", EXPFILL }},
+ { &ei_sip_authorization_invalid, { "sip.authorization.invalid", PI_PROTOCOL, PI_WARN, "Invalid authorization reponse for known credentials", EXPFILL }}
};
module_t *sip_module;
expert_module_t* expert_sip;
uat_t* sip_custom_headers_uat;
+ uat_t* sip_authorization_users_uat;
static tap_param sip_stat_params[] = {
{ PARAM_FILTER, "filter", "Filter", NULL, TRUE }
@@ -6796,6 +7007,13 @@ void proto_register_sip(void)
UAT_END_FIELDS
};
+ static uat_field_t sip_authorization_users_uat_fields[] = {
+ UAT_FLD_CSTRING(sip_authorization_users, username, "Username", "SIP authorization username"),
+ UAT_FLD_CSTRING(sip_authorization_users, realm, "Realm", "SIP authorization realm"),
+ UAT_FLD_CSTRING(sip_authorization_users, password, "Password", "SIP authorization password"),
+ UAT_END_FIELDS
+ };
+
/* Register the protocol name and description */
proto_sip = proto_register_protocol("Session Initiation Protocol", "SIP", "sip");
proto_raw_sip = proto_register_protocol("Session Initiation Protocol (SIP as raw text)",
@@ -6895,6 +7113,33 @@ void proto_register_sip(void)
"A table to define custom SIP header for which fields can be setup and used for filtering/data extraction etc.",
sip_custom_headers_uat);
+ prefs_register_bool_preference(sip_module, "validate_authorization",
+ "Validate SIP authorization",
+ "Validate SIP authorizations with known credentials",
+ &global_sip_validate_authorization);
+
+ sip_authorization_users_uat = uat_new("SIP authorization users",
+ sizeof(authorization_user_t),
+ "authorization_users_sip",
+ TRUE,
+ &sip_authorization_users,
+ &sip_authorization_num_users,
+ /* specifies named fields, so affects dissection
+ and the set of named fields */
+ UAT_AFFECTS_DISSECTION|UAT_AFFECTS_FIELDS,
+ NULL,
+ authorization_users_copy_cb,
+ authorization_users_update_cb,
+ authorization_users_free_cb,
+ NULL,
+ NULL,
+ sip_authorization_users_uat_fields
+ );
+
+ prefs_register_uat_preference(sip_module, "authorization_users_sip", "SIP authorization users",
+ "A table to define user credentials used for validating authorization attempts",
+ sip_authorization_users_uat);
+
register_init_routine(&sip_init_protocol);
register_cleanup_routine(&sip_cleanup_protocol);
heur_subdissector_list = register_heur_dissector_list("sip", proto_sip);