Talos Vulnerability Report

TALOS-2023-1695

Milesight UR32L luci2-io file-export mib directory traversal vulnerability

July 6, 2023
CVE Number

CVE-2023-23547

SUMMARY

A directory traversal vulnerability exists in the luci2-io file-export mib functionality of Milesight UR32L v32.3.0.5. A specially crafted network request can lead to arbitrary file read. An attacker can send a network 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

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

CWE

CWE-22 - Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’)

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 router offers many CGI endpoints, one of which is cgi-bin/file-export. This CGI endpoint allows for the various configuration files to be exported, based on parameters provided. This API is managed by the luci2-io’s file-export function. The file-export function parses the parameters provided, checks if the session provided is valid and then returns, if the parameters are valid, the content of the requested configuration file. Following the file-export function:

void file-export(void)

{
  [... variable declaration ...]

  [...]
  key_value_array[0].key = "sessionid";
  key_value_array[0].value = (char *)0x0;
  key_value_array[1].key = "type";
  key_value_array[1].value = (char *)0x0;
  key_value_array[2].key = "file";
  key_value_array[2].value = (char *)0x0;
  [...]
  is_ok = parse_data_and_url_decode(key_value_array,3);                                                  [1]
  if ((is_ok == 0) ||
     (is_ok = check_permission(key_value_array[0].value,"export","read"),
     file_variable = key_value_array[2].value, type_variable = key_value_array[1].value,
     is_ok == 0)) {
    type_variable = "Export permission denied";
  }
  else {
    cgi_struct_cur = cgi_helper_structure;
    struct_idx = 0;
    do {
      iVar2 = strcmp(type_variable,cgi_struct_cur->type);
      if (iVar2 == 0) {
        if (cgi_struct_cur->file == (char *)0x0) {
          snprintf(EXPORT_FILEPATH,0x80,"%s%s",cgi_helper_structure[struct_idx].filepath/dirpath,
                   file_variable);                                                                       [2]
          cgi_helper_structure[struct_idx].filepath/dirpath = EXPORT_FILEPATH;                           [3]
        }
        else {
          [...]
        }
        iVar2 = ::pipe((int *)pipe_obj);
        if (iVar2 == 0) {
          __pid = fork();
          if (__pid != -1) {
            if (__pid == 0) {
              iVar2 = -1;
              close((int)pipe_obj[0]);
              cgi_helper_structure[struct_idx].pipe_write_side = pipe_obj[1];
              (*(code *)cgi_helper_structure[struct_idx].read_file_func)
                        (cgi_helper_structure + struct_idx);                                             [4]
              close((int)pipe_obj[1]);
            }
            [...]
          }
          [...]
        }
        [...]
      }
      struct_idx = struct_idx + 1;
      cgi_struct_cur = cgi_struct_cur + 1;
    } while (struct_idx != 0x61);
    type_variable = "Export : incorrect file type";
  }
  [...]
}

The function uses a static array with the different configuration info. Following the relevant portion of an element of this array:

char *    type                             = "mib"
char *    file                             = 0
char *    filepath/dirpath                 = "/usr/share/snmp/mibs"
char *    filename                         = "mib.txt"
char *    respone_content_type             = "text/plain"
code *    read_file_func                   = read_file                # function at 0x13a30
[...]

The relevant request’s parameters are: type and file. The function will iterate over all the elements of the static array and will perform an action when the type field of the considered element is equal to the type parameter provided. Above it is shown the mib element.

The function parses, at [1], the parameters provided. The function then iterates over all the elements in the static array as soon as the type matches the provided one. If the file field is not empty in the considered element, the file parameter is used at [2] to compose the pathname of the desired file using the filepath/dirpath field of the specified element. For instance, using the element shown above, at [2], the composed string will have the following form: /usr/share/snmp/mibs/<file_param>. Then the field filepath/dirpath is modified to be equal to the just-composed string.

In the case of the mib type, at [4], the read_file function will be called, and the string composed at [2] will be used as pathname. Because the composed string at [2] is not sanitized, a directory traversal vulnerability exists in the file-export function, which can lead to arbitrary file read.

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.