[mod_sofia] more fixes around md5 hash infoleak (tricky cfg).

* [mod_sofia] md5 hash infoleak fixes: (explictly allow host set in gw params 'register_proxy' and 'outbound_proxy' to challenge too).

"We have tightened security for digest authentication on gateways.
If you have gateways configured using dns and not an ip address,
you may need to configure gw-auth-acl in gateway configuration to
specify the IP addresses you can send auth challenges to for this gateway.
This is meant to better secure who we will send auth challenges." (Mike Jerris)

[mod_sofia] add REGISTER/401 unit-tests (sipp based)

[mod_sofia] add INVITE/407 unit-test (sipp-based)

* Cleanup spacing

Co-authored-by: Andrey Volk <andywolk@gmail.com>
This commit is contained in:
Dragos Oancea 2021-09-08 18:20:20 +03:00 committed by Andrey Volk
parent ee9214c6fb
commit 69cdaac60b
10 changed files with 520 additions and 27 deletions

View File

@ -527,6 +527,9 @@ struct sofia_gateway {
char *register_proxy;
char *register_sticky_proxy;
char *outbound_sticky_proxy;
char *register_proxy_host_cfg; /* hold only the IP or the hostname, no port, no "sip:" or "sips:" prefix */
char *outbound_proxy_host_cfg;
char *proxy_host_cfg;
char *register_context;
char *expires_str;
char *register_url;
@ -1258,6 +1261,7 @@ void sofia_glue_pause_jitterbuffer(switch_core_session_t *session, switch_bool_t
void sofia_process_dispatch_event(sofia_dispatch_event_t **dep);
void sofia_process_dispatch_event_in_thread(sofia_dispatch_event_t **dep);
char *sofia_glue_get_host(const char *str, switch_memory_pool_t *pool);
char *sofia_glue_get_host_from_cfg(const char *str, switch_memory_pool_t *pool);
void sofia_presence_check_subscriptions(sofia_profile_t *profile, time_t now);
void sofia_msg_thread_start(int idx);
void crtp_init(switch_loadable_module_interface_t *module_interface);

View File

@ -3950,8 +3950,10 @@ static void parse_gateways(sofia_profile_t *profile, switch_xml_t gateways_tag,
contact_host = val;
} else if (!strcmp(var, "register-proxy")) {
register_proxy = val;
gateway->register_proxy_host_cfg = sofia_glue_get_host_from_cfg(register_proxy, gateway->pool);
} else if (!strcmp(var, "outbound-proxy")) {
outbound_proxy = val;
gateway->outbound_proxy_host_cfg = sofia_glue_get_host_from_cfg(outbound_proxy, gateway->pool);
} else if (!strcmp(var, "distinct-to")) {
distinct_to = switch_true(val);
} else if (!strcmp(var, "destination-prefix")) {
@ -4025,6 +4027,8 @@ static void parse_gateways(sofia_profile_t *profile, switch_xml_t gateways_tag,
proxy = realm;
}
gateway->proxy_host_cfg = sofia_glue_get_host_from_cfg(proxy, gateway->pool);
if (!switch_true(register_str)) {
gateway->state = REG_STATE_NOREG;
gateway->status = SOFIA_GATEWAY_UP;

View File

@ -3539,6 +3539,42 @@ char *sofia_glue_get_profile_url(sofia_profile_t *profile, char *remote_ip, cons
return url;
}
/* gets the IP or HOST from a sip uri or from x.x.x.x:port format */
char *sofia_glue_get_host_from_cfg(const char *uri, switch_memory_pool_t *pool)
{
char *host = NULL;
const char *s;
char *p = NULL;
if (zstr(uri)) {
return NULL;
}
if ((s = switch_stristr("sip:", uri)) && s == uri) {
s += 4;
} else if ((s = switch_stristr("sips:", uri)) && s == uri) {
s += 5;
}
if (!s) {
s = uri;
}
host = switch_core_strdup(pool, s);
if ((p = strchr(host, ']'))) {
if (*(p + 1) == ':') {
*(p + 1) = '\0';
}
} else {
if ((p = strrchr(host, ':'))) {
*p = '\0';
}
}
return host;
}
/* For Emacs:
* Local Variables:
* mode:c

View File

@ -2508,12 +2508,11 @@ switch_bool_t sip_resolve_compare(const char *domainname, const char *ip, sofia_
if (strchr(ip, ':')) {
ipv6 = SWITCH_TRUE;
}
ret = dig_all_srvs_simple(dig, domainname, ip, ipv6);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "verify 1\n");
if (!ret) {
answers = dig_addr_simple(dig, host, ipv6?sres_type_aaaa:sres_type_a);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "verify 2\n");
ret = verify_ip(answers, ip, ipv6);
}
@ -2524,6 +2523,36 @@ out:
return ret;
}
static switch_bool_t is_host_from_gateway(const char *remote_ip, sofia_gateway_t *gateway)
{
switch_bool_t ret = SWITCH_FALSE;
char *hosts[3]; /* check the 3 places where we keep IP/hostname */
int i;
hosts[0] = gateway->proxy_host_cfg;
hosts[1] = gateway->register_proxy_host_cfg;
hosts[2] = gateway->outbound_proxy_host_cfg;
for (i = 0; i < 3; i++) {
if (zstr(hosts[i])) {
continue;
}
if (host_is_ip_address(hosts[i])) {
if (!strcmp(hosts[i], remote_ip)) {
ret = SWITCH_TRUE;
}
if (ret) break;
} else {
ret = sip_resolve_compare(hosts[i], remote_ip, gateway->register_transport);
if (ret) break;
}
}
return ret;
}
static switch_bool_t is_legitimate_gateway(sofia_dispatch_event_t *de, sofia_gateway_t *gateway)
{
char remote_ip[80] = { 0 };
@ -2531,27 +2560,16 @@ static switch_bool_t is_legitimate_gateway(sofia_dispatch_event_t *de, sofia_gat
sofia_glue_get_addr(de->data->e_msg, remote_ip, sizeof(remote_ip), NULL);
/* setting param "gw-auth-acl" supersedes everything */
if (gateway->gw_auth_acl) {
ret = switch_check_network_list_ip(remote_ip, gateway->gw_auth_acl);
if (!ret) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Challange from [%s] denied by gw-auth-acl.\n", remote_ip);
}
return ret;
} else {
char *register_host = sofia_glue_get_register_host(gateway->register_proxy);
const char *host = sofia_glue_strip_proto(register_host);
if (host_is_ip_address(host)) {
if (host && !strcmp(host, remote_ip)) {
ret = SWITCH_TRUE;
}
switch_safe_free(register_host);
return ret;
} else {
ret = sip_resolve_compare(host, remote_ip, gateway->register_transport);
switch_safe_free(register_host);
return ret;
}
return is_host_from_gateway(remote_ip, gateway);
}
}

View File

@ -322,11 +322,41 @@
<param name="username" value="not-used"/>
<param name="password" value="not-used"/>
<param name="proxy" value="127.0.0.1:5080"/>
<param name="realm" value="no.local"/>
<param name="register" value="false"/>
<param name="retry-seconds" value="30"/>
<param name="dtmf-type" value="rfc2833"/>
<variables>
<variable name="rtp_secure_media" value="false" direction="outbound"/>
<variables>
<variable name="rtp_secure_media" value="false" direction="outbound"/>
</variables>
</gateway>
<gateway name="testgw">
<param name="username" value="1001"/>
<param name="password" value="1234"/>
<param name="proxy" value="$${local_ip_v4}:6080"/>
<param name="register" value="true"/>
<param name="realm" value="unresolvable.local"/>
<param name="outbound_proxy" value="$${local_ip_v4}:6080"/>
<param name="register_proxy" value="127.0.0.1:6080"/>
<param name="retry-seconds" value="30"/>
<param name="dtmf-type" value="rfc2833"/>
<variables>
<variable name="rtp_secure_media" value="false" direction="outbound"/>
</variables>
</gateway>
<gateway name="testgw-noreg">
<param name="username" value="1001"/>
<param name="password" value="1234"/>
<param name="proxy" value="$${local_ip_v4}:6080"/>
<param name="register" value="false"/>
<param name="realm" value="freeswitch.org"/>
<param name="outbound_proxy" value="$${local_ip_v4}:6080"/>
<param name="register_proxy" value="127.0.0.1:6080"/>
<param name="retry-seconds" value="30"/>
<param name="dtmf-type" value="rfc2833"/>
<variables>
<variable name="rtp_secure_media" value="false" direction="outbound"/>
</variables>
</gateway>
</gateways>
@ -365,7 +395,7 @@
<param name="rtp-hold-timeout-sec" value="1800"/>
<param name="session-timeout" value="600"/>
<param name="minimum-session-expires" value="90"/>
<param name="tls" value="false"/>
<param name="tls" value="false"/>
</settings>
</profile>

View File

@ -57,6 +57,22 @@ static switch_bool_t has_ipv6()
return SWITCH_TRUE;
}
static void register_gw()
{
switch_stream_handle_t stream = { 0 };
SWITCH_STANDARD_STREAM(stream);
switch_api_execute("sofia", "profile external register testgw", NULL, &stream);
switch_safe_free(stream.data);
}
static void unregister_gw()
{
switch_stream_handle_t stream = { 0 };
SWITCH_STANDARD_STREAM(stream);
switch_api_execute("sofia", "profile external unregister testgw", NULL, &stream);
switch_safe_free(stream.data);
}
static int start_sipp_uac(const char *ip, int remote_port,const char *scenario_uac, const char *extra)
{
char *cmd = switch_mprintf("sipp %s:%d -nr -p 5062 -m 1 -s 1001 -recv_timeout 10000 -timeout 10s -sf %s -bg %s", ip, remote_port, scenario_uac, extra);
@ -65,24 +81,29 @@ static int start_sipp_uac(const char *ip, int remote_port,const char *scenario_u
printf("%s\n", cmd);
switch_safe_free(cmd);
switch_sleep(1000 * 1000);
return sys_ret;
}
static int start_sipp_uas(const char *ip, int listen_port, const char *scenario_uas, const char *extra)
{
char *cmd = switch_mprintf("sipp %s -p %d -nr -m 1 -s 1001 -recv_timeout 10000 -timeout 10s -sf %s -bg %s", ip, listen_port, scenario_uas, extra);
int sys_ret = switch_system(cmd, SWITCH_TRUE);
printf("%s\n", cmd);
switch_safe_free(cmd);
switch_sleep(1000 * 1000);
return sys_ret;
}
static void kill_sipp(void)
{
switch_system("pkill -x sipp", SWITCH_TRUE);
switch_sleep(1000 * 1000);
}
static void event_handler(switch_event_t *event)
{
const char *new_ev = switch_event_get_header(event, "Event-Subclass");
static void show_event(switch_event_t *event) {
char *str;
if (new_ev && !strcmp(new_ev, "sofia::gateway_invalid_digest_req")) {
test_success = 1;
}
/*print the event*/
switch_event_serialize_json(event, &str);
if (str) {
@ -91,6 +112,45 @@ static void event_handler(switch_event_t *event)
}
}
static void event_handler(switch_event_t *event)
{
const char *new_ev = switch_event_get_header(event, "Event-Subclass");
if (new_ev && !strcmp(new_ev, "sofia::gateway_invalid_digest_req")) {
test_success = 1;
}
show_event(event);
}
static void event_handler_reg_ok(switch_event_t *event)
{
const char *new_ev = switch_event_get_header(event, "Event-Subclass");
if (new_ev && !strcmp(new_ev, "sofia::gateway_state")) {
const char *state = switch_event_get_header(event, "State");
if (state && !strcmp(state, "REGED")) {
test_success++;
}
}
show_event(event);
}
static void event_handler_reg_fail(switch_event_t *event)
{
const char *new_ev = switch_event_get_header(event, "Event-Subclass");
if (new_ev && !strcmp(new_ev, "sofia::gateway_state")) {
const char *state = switch_event_get_header(event, "State");
if (state && !strcmp(state, "FAIL_WAIT")) {
test_success++;
}
}
show_event(event);
}
FST_CORE_EX_BEGIN("./conf-sipp", SCF_VG | SCF_USE_SQL)
{
FST_MODULE_BEGIN(mod_sofia, uac-uas)
@ -289,6 +349,132 @@ skiptest:
}
FST_TEST_END()
FST_TEST_BEGIN(register_ok)
{
const char *local_ip_v4 = switch_core_get_variable("local_ip_v4");
int sipp_ret;
switch_event_bind("sofia", SWITCH_EVENT_CUSTOM, NULL, event_handler_reg_ok, NULL);
sipp_ret = start_sipp_uas(local_ip_v4, 6080, "sipp-scenarios/uas_register.xml", "");
if (sipp_ret < 0 || sipp_ret == 127) {
fst_requires(0); /* sipp not found */
}
switch_sleep(1000 * 1000);
register_gw();
switch_sleep(5000 * 1000);
switch_event_unbind_callback(event_handler_reg_ok);
/* sipp should timeout, attempt kill, just in case.*/
kill_sipp();
fst_check(test_success);
test_success = 0;
}
FST_TEST_END()
FST_TEST_BEGIN(register_403)
{
const char *local_ip_v4 = switch_core_get_variable("local_ip_v4");
int sipp_ret;
switch_event_bind("sofia", SWITCH_EVENT_CUSTOM, NULL, event_handler_reg_fail, NULL);
sipp_ret = start_sipp_uas(local_ip_v4, 6080, "sipp-scenarios/uas_register_403.xml", "");
if (sipp_ret < 0 || sipp_ret == 127) {
fst_requires(0); /* sipp not found */
}
switch_sleep(1000 * 1000);
register_gw();
switch_sleep(5000 * 1000);
switch_event_unbind_callback(event_handler_reg_fail);
/* sipp should timeout, attempt kill, just in case.*/
kill_sipp();
fst_check(test_success);
test_success = 0;
}
FST_TEST_END()
FST_TEST_BEGIN(register_no_challange)
{
const char *local_ip_v4 = switch_core_get_variable("local_ip_v4");
int sipp_ret;
switch_event_bind("sofia", SWITCH_EVENT_CUSTOM, NULL, event_handler_reg_ok, NULL);
sipp_ret = start_sipp_uas(local_ip_v4, 6080, "sipp-scenarios/uas_register_no_challange.xml", "");
if (sipp_ret < 0 || sipp_ret == 127) {
fst_requires(0); /* sipp not found */
}
switch_sleep(1000 * 1000);
register_gw();
switch_sleep(5000 * 1000);
/*the REGISTER with Expires 0 */
unregister_gw();
switch_sleep(1000 * 1000);
register_gw();
switch_sleep(1000 * 1000);
switch_event_unbind_callback(event_handler_reg_ok);
/* sipp should timeout, attempt kill, just in case.*/
kill_sipp();
fst_check(test_success);
test_success = 0;
}
FST_TEST_END()
FST_TEST_BEGIN(invite_407)
{
const char *local_ip_v4 = switch_core_get_variable("local_ip_v4");
int sipp_ret;
switch_core_session_t *session;
switch_call_cause_t cause;
switch_status_t status;
switch_channel_t *channel;
char *to;
const int inv_sipp_port = 6082;
sipp_ret = start_sipp_uas(local_ip_v4, inv_sipp_port, "sipp-scenarios/uas_407.xml", "");
if (sipp_ret < 0 || sipp_ret == 127) {
fst_requires(0); /* sipp not found */
}
switch_sleep(1000 * 1000);
to = switch_mprintf("sofia/gateway/testgw-noreg/sipp@%s:%d", local_ip_v4, inv_sipp_port);
/*originate will fail if the 407 we get from sipp is dropped due to wrong IP.*/
status = switch_ivr_originate(NULL, &session, &cause, to, 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL);
fst_check(status == SWITCH_STATUS_SUCCESS);
/*test is considered PASSED if we get a session*/
if (!session) {
fst_requires(session);
}
switch_sleep(1000 * 1000);
channel = switch_core_session_get_channel(session);
switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
switch_core_session_rwunlock(session);
switch_safe_free(to);
/* sipp should timeout, attempt kill, just in case.*/
kill_sipp();
}
FST_TEST_END()
}
FST_MODULE_END()
}

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAS 407">
<recv request="INVITE" />
<send><![CDATA[
SIP/2.0 407 Authorization Required
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Proxy-Authenticate: Digest realm="freeswitch.org", nonce="47ebe028cda119c35d4877b383027d28da013815"
Content-Length: [len]
]]>
</send>
<recv request="ACK" timeout="1000">
</recv>
<recv request="INVITE" timeout="1000">
</recv>
<send>
<![CDATA[
SIP/2.0 180 Ringing
[last_Via:]
[last_From:]
[last_To:];tag=[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Length: 0
]]>
</send>
<pause milliseconds="1000"/>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:];tag=[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0
a=rtpmap:0 PCMU/8000
]]>
</send>
<recv request="ACK" timeout="1000">
</recv>
<!-- definition of the response time repartition table (unit is ms) -->
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
<!-- definition of the call length repartition table (unit is ms) -->
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
</scenario>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAS Registrar">
<recv request="REGISTER" />
<send><![CDATA[
SIP/2.0 401 Authorization Required
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
WWW-Authenticate: Digest realm="freeswitch.org", nonce="47ebe028cda119c35d4877b383027d28da013815"
Content-Length: [len]
]]>
</send>
<recv request="REGISTER" >
</recv>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:];tag=[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Length: 0
Expires: 60
]]>
</send>
<!-- definition of the response time repartition table (unit is ms) -->
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
<!-- definition of the call length repartition table (unit is ms) -->
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
</scenario>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAS Registrar">
<recv request="REGISTER" />
<send><![CDATA[
SIP/2.0 401 Authorization Required
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
WWW-Authenticate: Digest realm="freeswitch.org", nonce="81dc9bdb52d04dc20036dbd8313ed055"
Content-Length: [len]
]]>
</send>
<recv request="REGISTER" >
</recv>
<send>
<![CDATA[
SIP/2.0 403 Forbidden
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Length: 0
]]>
</send>
<!-- definition of the response time repartition table (unit is ms) -->
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
<!-- definition of the call length repartition table (unit is ms) -->
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
</scenario>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<scenario name="Basic UAS responder">
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
<!-- are saved and used for following messages sent. Useful to test -->
<!-- against stateful SIP proxies/B2BUAs. -->
<recv request="REGISTER" crlf="true">
</recv>
<send retrans="500">
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
]]>
</send>
<recv request="REGISTER" crlf="true">
</recv>
<send retrans="500">
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
]]>
</send>
<recv request="REGISTER" crlf="true">
</recv>
<send retrans="500">
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
]]>
</send>
<!-- definition of the response time repartition table (unit is ms) -->
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
<!-- definition of the call length repartition table (unit is ms) -->
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
</scenario>