Talos Vulnerability Report

TALOS-2023-1718

Milesight UR32L urvpn_client http_connection_readcb stack-based buffer overflow vulnerability

July 6, 2023
CVE Number

CVE-2023-24019

SUMMARY

A stack-based buffer overflow vulnerability exists in the urvpn_client http_connection_readcb functionality of Milesight UR32L v32.3.0.5. A specially crafted network packet can lead to a buffer overflow. An attacker can send a malicious packet 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.1 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-120 - Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)

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 a service called Milesight VPN, a VPN service that will connect to the Milesight VPN software. The vulnerability described in this report requires this service to be enabled by the admin, as it is disabled by default. The binary client used for this service is urvpn_client. This binary will connect to the specified Milesight VPN server. The first request is used to “authenticate” the router to the server. Essentially, the router will send information about itself to the server. After the request, the http_connection_readcb function will be executed to read the response of the server. Following the http_connection_readcb function:

void http_connection_readcb(undefined4 param_1,http_connection *connection)

{
  [... variable declaration ...]

  [...]
  associated_request = connection->associated_request;
  input_obj = bufferevent_get_input(param_1);
  evbuffer_search_eol(auStack_f0,input_obj,0,&line_length,1);
  tmp = (char *)evbuffer_get_length(input_obj);
  response_body_len = associated_request->content_size;
  inbody = associated_request->inbody;
  status = associated_request->status;
  iVar4 = line_length;
  log_message("httptransport.c",0x152,"http_connection_readcb",0,
              "libevent begin : http input receive size %d, req->response_body_len %d, 
              req->inbody % d, req->status %d line_length %d"
              ,tmp,response_body_len,inbody,status,line_length);
  if (((int)associated_request->content_size < 1) && (line_length == 0)) {
    log_message("httptransport.c",0x156,"http_connection_readcb",0,"Did not receive enough data",tmp
                ,response_body_len,inbody,status,iVar4);
    log_message("httptransport.c",0x157,"http_connection_readcb",0,"Did not receive enough data");
    return;
  }
  if (associated_request->content_size == 0xffffffff) {
    log_message("httptransport.c",0x15e,"http_connection_readcb",1,
                "req->response_body_len == -1, this is a fresh request, handle response header",tmp,
                response_body_len,inbody,status,iVar4);
    input_line = (char *)evbuffer_readln(input_obj,0,1);                                                [1]
    log_message("httptransport.c",0x165,"http_connection_readcb",0,"line %s",input_line);
    iVar1 = sscanf(input_line,"HTTP/%d.%d %d %s",http_major_version,http_minor_version,
                   &http_status_code,http_status_message);                                              [2]
    if (iVar1 < 1) {
      log_message("httptransport.c",0x169,"http_connection_readcb",3,"Couldn\'t parse HTTP response"
                 );
      probably_free_associated_req(&connection->associated_request,&associated_request);
      free(input_line);
      return;
    }
    free(input_line);
    associated_request->status = http_status_code;
    associated_request->content_size = 0;
  }
  [...]
}

At [1] the first line of the response is fetched, then at [2] is parsed, expecting to be of the HTTP/%d.%d %d %s form. So, the protocol version is parsed, as well as the status code and the status message. For containing the status message, the http_status_message stack buffer is used. Because no check is performed against the line received at [1], and because the http_status_message buffer is only 52 bytes long, this function is vulnerable to a stack-based buffer overflow that can occur at [2].

Because HTTPS is used for the connection to the server, it would seems impossible to man-in-the-middle this communication. Because of TALOS-2023-1705 this is actually possible because the client does not verify the server.

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.