CVE-2020-15260 (published 2021-03-10T23:15Z)

PJSIP is a free and open source multimedia communication library written in C language implementing standard based protocols such as SIP, SDP, RTP, STUN, TURN, and ICE. In version 2.10 and earlier, PJSIP transport can be reused if they have the same IP address + port + protocol. However, this is insufficient for secure transport since it lacks remote hostname authentication. Suppose we have created a TLS connection to `sip.foo.com`, which has an IP address `100.1.1.1`. If we want to create a TLS connection to another hostname, say `sip.bar.com`, which has the same IP address, then it will reuse that existing connection, even though `100.1.1.1` does not have certificate to authenticate as `sip.bar.com`. The vulnerability allows for an insecure interaction without user awareness. It affects users who need access to connections to different destinations that translate to the same address, and allows man-in-the-middle attack if attacker can route a connection to another destination such as in the case of DNS spoofing.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 6.8, MEDIUM (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-295
cwe_name: Improper Certificate Validation
description: The product does not validate, or incorrectly validates, a certificate.
extended_description: When a certificate is invalid or malicious, it might allow an attacker to spoof a trusted entity by interfering in the communication path between the host and client. The product might connect to a malicious host while believing it is a trusted host, or the product might be deceived into accepting spoofed data that appears to originate from a trusted host.
         

Fix information:
repo_url: https://github.com/pjsip/pjproject
commit_details: repo_url: https://github.com/pjsip/pjproject
author: sauwming
author_date: 2021-03-08 17:39:36+08:00
msg: Merge pull request from GHSA-8hcp-hm38-mfph

* Check hostname during TLS transport selection

* revision based on feedback

* remove the code in create_request that has been moved
repository: repo_url: https://github.com/pjsip/pjproject
repo_name: pjsip/pjproject
description: PJSIP project
repo_language: C
owner: pjsip
file_changes: filename: sip_dialog.h
change_type: ModificationType.MODIFY
diff: @@ -165,6 +165,7 @@ struct pjsip_dialog
     pjsip_route_hdr	route_set;  /**< Route set.			    */
     pj_bool_t		route_set_frozen; /**< Route set has been set.	    */
     pjsip_auth_clt_sess	auth_sess;  /**< Client authentication session.	    */
+    pj_str_t		initial_dest;/**< Initial destination host.  	    */
 
     /** Session counter. */
     int			sess_count; /**< Number of sessions.		    */

programming_language: C
filename: sip_dialog.c
change_type: ModificationType.MODIFY
diff: @@ -467,6 +467,10 @@ pj_status_t create_uas_dialog( pjsip_user_agent *ua,
 
     /* Save the remote info. */
     pj_strdup(dlg->pool, &dlg->remote.info_str, &tmp);
+    
+    /* Save initial destination host from transport's info */
+    pj_strdup(dlg->pool, &dlg->initial_dest,
+    	      &rdata->tp_info.transport->remote_name.host);
 
 
     /* Init remote's contact from Contact header.
@@ -1192,6 +1196,12 @@ static pj_status_t dlg_create_request_throw( pjsip_dialog *dlg,
 	    return status;
     }
 
+    /* Copy the initial destination host to tdata. This information can be
+     * used later by transport for transport selection.
+     */
+    if (dlg->initial_dest.slen)
+    	pj_strdup(tdata->pool, &tdata->dest_info.name, &dlg->initial_dest);
+
     /* Done. */
     *p_tdata = tdata;
 
@@ -1822,6 +1832,11 @@ static void dlg_update_routeset(pjsip_dialog *dlg, const pjsip_rx_data *rdata)
      * transaction as the initial transaction that establishes dialog.
      */
     if (dlg->role == PJSIP_ROLE_UAC) {
+    	/* Save initial destination host from transport's info. */
+    	if (!dlg->initial_dest.slen) {
+    	    pj_strdup(dlg->pool, &dlg->initial_dest,
+    	      	      &rdata->tp_info.transport->remote_name.host);
+    	}
 
 	/* Ignore subsequent request from remote */
 	if (msg->type != PJSIP_RESPONSE_MSG)

programming_language: C
filename: sip_transport.c
change_type: ModificationType.MODIFY
diff: @@ -2335,6 +2335,19 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
 		    if (!tp_iter->tp->is_shutdown &&
 			!tp_iter->tp->is_destroying)
 		    {
+			if ((type & PJSIP_TRANSPORT_SECURE) && tdata) {
+			    /* For secure transport, make sure tdata's
+			     * destination host matches the transport's
+			     * remote host.
+			     */
+			    if (pj_stricmp(&tdata->dest_info.name,
+				  	   &tp_iter->tp->remote_name.host))
+			    {
+			    	tp_iter = tp_iter->next;
+			    	continue;
+			    }
+			}
+
 			if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER &&
 			    sel->u.listener)
 			{

programming_language: C
filename: sip_util.c
change_type: ModificationType.MODIFY
diff: @@ -1417,7 +1417,10 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_request_stateless(pjsip_endpoint *endpt,
      */
     if (tdata->dest_info.addr.count == 0) {
 	/* Copy the destination host name to TX data */
-	pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);
+	if (!tdata->dest_info.name.slen) {
+	    pj_strdup(tdata->pool, &tdata->dest_info.name,
+	    	      &dest_info.addr.host);
+	}
 
 	pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, stateless_data,
 			     &stateless_send_resolver_callback);
@@ -1810,8 +1813,10 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt,
 	}
     } else {
 	/* Copy the destination host name to TX data */
-	pj_strdup(tdata->pool, &tdata->dest_info.name, 
-		  &res_addr->dst_host.addr.host);
+	if (!tdata->dest_info.name.slen) {
+	    pj_strdup(tdata->pool, &tdata->dest_info.name, 
+		      &res_addr->dst_host.addr.host);
+	}
 
 	pjsip_endpt_resolve(endpt, tdata->pool, &res_addr->dst_host, 
 			    send_state, &send_response_resolver_cb);

programming_language: C