TL;DR;
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 CVE-2021-34730 (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 DD-WRT and SSD-Disclosure 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 verify if you are affected by the issues reported in this blog post, 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 withbcm
(e.g., bcm_atmctl
, bcmcastctl
, 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
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
:
* $Id: upnp_ssdp.c 305062 2011-12-26 10:48:21Z kenlo $Latest appearance of heap buffer overflow in upstream, never saw a public fix. Not sure when it was fixed (if ever).
* $Id: InternetGatewayDevice.c 241192 2011-02-17 21:52:25Z gmo $
The Vulnerabilities
Stack Buffer Overflow via ssdp_msearch
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:- 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
. The SSDP handling code defines a ssdp_methods
array:
struct upnp_method ssdp_methods[] = { {"M-SEARCH ", sizeof("M-SEARCH ")-1, METHOD_MSEARCH, ssdp_msearch}, {0, 0, 0, 0} };This array matches the HTTP verb from the incoming SSDP message (M-SEARCH) to a specific handler (
ssdp_msearch
).
In ssdp_msearch
, headers HOST
, MAN
, and ST
are parsed successively. On line 31, the code checks whether the ST
header starts with uuid:
. If it does, an insecure call to strcpy
is made on line 35 to copy the rest of that header line into a fixed sized buffer (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); } }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"This bug is fully exploitable given that we control the program counter. In the example below, we demonstrate that we control registers
$r4
through $r11
on an ARM-based target.
We developed a quick proof-of-concept for that bug with Metasploit targeting Cisco RV130, based on our initial work targeting these brands of devices. You can see the exploit in action below.
Heap Buffer Overflow via upnp_portmap_add
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: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>There’s a heap buffer overflow via the UPnP port mapping function, the overflow happens in a linked list of port mapping entries (
map
).
On line 3, a mapping between the SOAP action AddPortMapping
and its expected argument and handler is defined (excerpt from ./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} };On line 26,
upnp_portmap_add
is called with arguments parsed from the SOAP envelope:
/* << 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
starts by requesting a portmap
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 strcpy
are made, which is the core of this vulnerability:
/* 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; }Trigger:
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>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
$r4
through $r11
on an ARM-based target.
Setting NewEnabled
to 0 turns it into a stack buffer overflow, and we can bypass the NewRemoteHost
validation (checking that it corresponds to the WAN IP) by setting a string value rather than a dotted decimal IP value.
Previous Discoveries
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 https://svn.dd-wrt.com/browser/src/router/upnp/src/ssdp.c?rev=11589#L385. We identified the heap buffer overflow in the code at https://svn.dd-wrt.com/browser/src/router/upnp/device/InternetGatewayDevice/InternetGatewayDevice.c?rev=11589#L215. The vulnerability was present in older version but thestrcpy
call recently changed to strlcpy
, which intrigued us.
We checked when this change was made:
svn blame ssdp.c | grep strlcpy 45724 brainslaye strlcpy(name, st, sizeof(name));Looked at the commit itself:
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. */And the commit message:
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 ------------------------------------------------------------------------Then by googling for “DD-WRT stack overflow”, we ended up finding this advisory from SSD Disclosure: https://ssd-disclosure.com/ssd-advisory-dd-wrt-upnp-buffer-overflow/. 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:
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));Commit message:
svn log -r 45725 ------------------------------------------------------------------------ r45725 | brainslayer | 2021-02-09 10:11:39 +0100 (Di, 09 Feb 2021) | 1 line rework other potential insecure code ------------------------------------------------------------------------
Let the Hunt Begin
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:- 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.
Mapping the Supply Chain
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
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:
- 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.
- 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.
- 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.
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 an automated Product Cybersecurity & Compliance Platform (PCCP) 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
Marketing Manager
sara.fortmann@onekey.com
euromarcom public relations GmbH
+49 611 973 150
team@euromarcom.de
RELATED RESEARCH ARTICLES
The X in XFTP Stands For eXecute
Find out how our platform enhances firmware security by identifying vulnerabilities & bugs in ICT products, ensuring compliance with DORA & NIS2 directive.
Ready to automate your Product Cybersecurity & Compliance?
Make cybersecurity and compliance efficient and effective with ONEKEY.