Talos Vulnerability Report

TALOS-2023-1714

Milesight UR32L vtysh_ubus tcpdump_start_cb OS command injection vulnerability

July 6, 2023
CVE Number

CVE-2023-22653

SUMMARY

An OS command injection vulnerability exists in the vtysh_ubus tcpdump_start_cb functionality of Milesight UR32L v32.3.0.5. A specially crafted HTTP request can lead to command execution. An authenticated attacker can send an HTTP request to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Milesight UR32L v32.3.0.5

PRODUCT URLS

UR32L - https://www.milesight-iot.com/cellular/router/ur32l/

CVSSv3 SCORE

8.8 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-78 - Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)

DETAILS

The Milesight UR32L is an industrial cellular router. The router features include support for multiple VPNs, a router console shell, firewall and many others.

The Milesight router offers several functionalities through the /cgi endpoint. The “core” functionality we are considering is called yruo_tools. In this “core” there is one function called “tcpdump_start”. This API is used to execute the command “tcpdump” using the provided data, following the vtysh_ubus’s tcpdump_start_cb function, responsible for managing the “tcpdump_start” functionality:

void tcpdump_start_cb(undefined4 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4,
                     undefined4 *param_5)

{
    [... variable declaration ...]

    [... variable initialization ...]
    [...]
    if ((param_advanced == (blob_attr *)0x0) ||
         (advanced_value = (char *)blobmsg_get_string(param_advanced), *advanced == '\0')) {
        [...]
        interface_value = (char *)blobmsg_get_string(param_interface]);
        [...]
        strncpy(interface_ptr,interface_value,0x80);
        snprintf(last_param.interface,0x80,"%s",interface_ptr);
        is_equal = strcmp(interface_ptr,"Any");
        if (is_equal == 0) {
          interface_ptr = "any";
          interface_ptr_dup = zstrdup(1,interface_ptr);
        }
        else {
          interface_ptr_dup = if_name_display2ori(interface_ptr);
          [...]
        }
        snprintf(tcpdump_options_string,0x100,"-i %s",interface_ptr_dup);
        [...]
        if (0 < (int)port) {
            last_param.port = port;
            len = strlen(tcpdump_options_string);
            snprintf(tcpdump_options_string + len,0x100 - len," port %d",port);                       [1]
        }
        [...]
        if (param_ip != (blob_attr *)0x0) {
            ip_value = (char *)blobmsg_get_string(param_ip);
            strncpy(host_string,ip_value,0x80);
            if (*host_string != 0) {
                snprintf(last_param.host,0x80,"%s",host_string);
                len = strlen(tcpdump_options_string);
                if ((int)port < 0) {
                  temp = "";
                }
                else {
                  temp = " and";
                }
                snprintf(tcpdump_options_string + len,0x100 - len,"%s host %s",temp,host_string);       [2]
            }
        }
        [...]
  }
  else {
    strncpy(advanced_param_buff,advanced_value,0x100);
    strtok(advanced_param_buff,";");
    strtok(advanced_param_buff,"|");
    temp = advanced_param_buff._0_4_ & 0xff;
    if (temp != 0) {
      snprintf(last_param.advance,0x100,"%s",advanced_param_buff);
      snprintf(tcpdump_options_string,0x100," %s",advanced_param_buff);                                 [3]
      [...]
    }
  }
  [... populate the dest_location variable with the destination of the recorded pcap ...]
  [...]
  len = strlen(tcpdump_options_string);
  snprintf(tcpdump_options_string + len,0x100 - len," -w %s",dest_location);
  snprintf(shell_command,0x200,"%s %s \"%s\" \"%s\" \"%s\" 2>&1 &","/usr/sbin/webtools.sh","tcpdump"
           ,tcpdump_options_string,dest_location,"/tmp/webtcpdump.lock");                               [4]
  [...]
  system(shell_command);                                                                                [5]
  [...]
}

This function parses, at most, four possible params: “interface”, “ip”, “port” and “advanced”. If the “advanced” param is present and not empty, the other three are ignored. Otherwise, if the “advanced” param is not present or its value is empty, then the function will parse the “interface”, “ip” and “port”.

Eventually the code at [4] is reached and the string '/usr/sbin/webtools.sh tcpdump "<tcpdump_options_string>" "dest_location" "/tmp/webtcpdump.lock" 2>&1 &' is composed. The tcpdump_options_string is composed using the provided parameters. For instance, at [1] is appended the ' port <port>' string, and at [2] the ' host <host>' or ' and host <host>' is appended, based on the presence of the “port” parameter. Otherwise, if the “advanced” parameter is present, neither [1] or [2] are reached, but instead the code at [3] is executed.

Following are two example of commands that will result in the same composed string. Note that these commands must be sent by an authenticated user.

{
    "id":60,
    "execute":1,
    "core":"yruo_tools",
    "function":"tcpdump_start",
    "values":[
        {
            "interface":"Any",
            "ip":"192.168.0.100",
            "port":12345,
            "advanced":""
        }
    ]
}

The above command uses the “interface”, “ip” and “port” params.

{
    "id":60,
    "execute":1,
    "core":"yruo_tools",
    "function":"tcpdump_start",
    "values":[
        {
            "interface":"",
            "ip":"",
            "port":"",
            "advanced":"-i any port 12345 and host 192.168.0.100"
        }
    ]
}

The above command uses only the “advanced” param.

Eventually the string composed at [4] will be used as argument of the system function at [5]. No particular checks are performed against the parameters provided, which can lead to an OS command injection.

VENDOR RESPONSE

Since the maintainer of this software did not release a patch during the 90 day window specified in our policy, we have now decided to release the information regarding this vulnerability, to make users of the software aware of this problem. See Cisco’s Coordinated Vulnerability Disclosure Policy for more information: https://tools.cisco.com/security/center/resources/vendor_vulnerability_policy.html

TIMELINE

2023-02-14 - Initial Vendor Contact
2023-02-21 - Vendor Disclosure
2023-07-06 - Public Release

Credit

Discovered by Francesco Benvenuto of Cisco Talos.