Lucene search

K
talosTalos IntelligenceTALOS-2022-1451
HistoryJan 26, 2022 - 12:00 a.m.

Reolink RLC-410W netserver parse_command_list memory corruption vulnerability

2022-01-2600:00:00
Talos Intelligence
www.talosintelligence.com
8
reolink rlc-410w
netserver
memory corruption

CVSS2

6.4

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

NONE

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:N/I:P/A:P

CVSS3

8.2

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

LOW

Availability Impact

HIGH

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

EPSS

0.001

Percentile

33.4%

Summary

A memory corruption vulnerability exists in the netserver parse_command_list functionality of reolink RLC-410W v3.0.0.136_20121102. A specially-crafted HTTP request can lead to an out-of-bounds write. An attacker can send an HTTP request to trigger this vulnerability.

Tested Versions

Reolink RLC-410W v3.0.0.136_20121102

Product URLs

RLC-410W - <https://reolink.com/us/product/rlc-410w/&gt;

CVSSv3 Score

9.3 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:H

CWE

CWE-20 - Improper Input Validation

Details

The Reolink RLC-410W is a WiFi security camera. The camera includes motion detection functionalities and various methods to save the recordings.

The RLC-410W offers several APIs functionalities, once logged, through a binary called netserver. A specially crafted request to netserver can lead to write a null byte in a partially controllable address due to improper input validation.

The function responsible for receiving the API requests, in netserver, is recv_command:

undefined4 recv_command(netserver_session *session)
{
  [...]
  fd = bc_event_get_fd(*(undefined4 *)&session-&gt;field_0x30);
  max_body_read = 10;
  if (fd &lt; 0) {
    [...]
  }
  else {
    received_data = session-&gt;received_data;
    while (session-&gt;header_len &lt;= received_data) {
      session_data = session-&gt;data;
PARSE_DATA:
      data_size = session_data-&gt;data_size;
      if (0x9c40 &lt; (int)data_size) {
        [... Invalid size ...]
      }
      if (0 &lt; (int)data_size) {
        iVar2 = recv_body(session,fd,data_size);                                                        [1]
        [...]
      }
      max_body_read = max_body_read + -1;
      parse_recved_data(session);                                                                       [2]
      if (max_body_read == 0) {
        return 1;
      }
      received_data = session-&gt;received_data;
    }
    iVar2 = recv_header(session,fd);                                                                    [3]
    if (iVar2 != -2) {
      [...]
      session_data = session-&gt;data;
      goto PARSE_DATA;
    }
    [...]
} At `[3]` is called the function responsible for receiving the header data. If `data_size`, a value inside the received header, is greater than 0 and less or equal than 0x9c40, at `[1]`, the `recv_body` is called. This function will receive the remaining part of the data. Eventually, at `[2]`, the `parse_recved_data` is called:

uint parse_recved_data(netserver_session *session)

{
  [...]
  if (session-&gt;maybe_parse_state == 0) {
    iVar1 = version_detect();
    if (-1 &lt; iVar1) {
      return 0;
    }
    printf("session:%s version detect failed\n",session-&gt;client_ip);
    c_client_session::state_set(session,2);
  }
  else if (session-&gt;maybe_parse_state == 2) {
    session_data = session-&gt;data;
    if (session_data-&gt;magic == (session-&gt;cmd_list).magic) {
      node = (netserver_session_cmd_node *)
             c_client_session::cmd_init(session,session_data-&gt;data_size + 0x18,0);                      [4]
      if (node != (netserver_session_cmd_node *)0x0) {
        __src = session-&gt;data;
        node-&gt;status = NEW;
        node-&gt;command_type = 0;
        memcpy(node-&gt;recv_data,__src,node-&gt;recv_len);
        session-&gt;received_data = session-&gt;received_data - node-&gt;recv_len;
        node-&gt;cmd = session_data-&gt;cmd;
        c_client_session::cmd_add(session,node);                                                        [5]
        [...]
} This function, in some specific case, is called twice for the same connection. The first time, calling `version_detect`, it will verify the correctness of the provided header and set some data based on the checks. The second time, calling the `cmd_init` function at `[4]`, a `cmd` object is created and appended, at `[5]`, to the list of commands that will latter on be executed.

The parse_recved_data:

undefined4
version_detect(netserver_session *session,undefined4 param_2,undefined4 param_3,void *param_4)

{
  [...]
  data = session-&gt;data;
  if (data-&gt;cmd != 1) {
    [... fail ...]
  }
  data_magic = data-&gt;magic;
  if (data_magic == 0xabcdef0) {
    session-&gt;choosen_magic = magic_0xabcdef0;
  }
  else {
    if (data_magic != 0xfedcba0) {
      [... fail ...]
    }
    session-&gt;choosen_magic = magic_0xfedcba0;
  }
  version_related = *(uint *)&data-&gt;encryption_type_related;
  (session-&gt;cmd_list).magic = data_magic;
  version_2 = version_related &gt;&gt; 24;
  [...]
  session-&gt;parse_state = 2;                                                                             [6]
  session-&gt;header_len = 0x18;                                                                           [7]
  [...]
}

This function will verify the correctness of the provided header and set some data based on the checks. One of the action performed is to distinguish between the request of a nonce, to perform a login, and a normal API request, allegedly, performed after the login. The parse_state, at [6], is changed to 2, this will allow to execute, in case of an API request, the second branch of parse_recved_data when called again. The header len is changed, at [7], to 0x18. Its original value was 0x14. This is because, in case of a normal API request, the header is extend by 4 bytes. This last header field represents the length of the provided API XML data provided.

Then the commands appended at [5] are parsed by the parse_command_list function:

undefined4 parse_command_list(netserver_session *session)

{
  [...]

  cmd_node_cur = (session-&gt;cmd_list).node_base;
  [...]
  do {
    if ((netserver_cmd_list *)cmd_node_cur == &session-&gt;cmd_list) {
      return 0;
    }
    node_data = cmd_node_cur-&gt;node_data;
    node_status = node_data-&gt;status;
    if (node_status == PROGRESS) {
      [...]
    }
    else {
      if (node_status == COMPELTED) {
        [...]
      }
      if (node_status == NEW) {
        netserver_session_data = node_data-&gt;recv_data;
            [...]
            XML_LEN = &node_data-&gt;recv_data-&gt;xml_length;
            [...]
            if(XML_LEN &lt; 0){                                                                            [8]
                [... ERROR ...]
            }
            [...]
            end_of_xml = &node_data-&gt;recv_data-&gt;xml_data + XML_LEN;                                     [9]
            xml_end_byte = (int)*end_of_xml;                                                            [10]
            *end_of_xml = '\0';                                                                         [11]
            [...]
      }
    }
    [...]
  } while( true );
}

Eventually the command will reach the parse_command_list function and at [9] the XML length, field in the header, will be used to seek the last byte of the XML data. This byte pointer, at [10], is dereferenced and then at [11] a null byte is placed in that position.

Since the only relevant check performed on the provided XML length is at [8], checking if the value is lower than 0, it is possible to write a null byte in a partially controllable heap address. Indeed, the value of the XML length, with positive value, is totally controllable, allowing partial control of the heap address, calculated at [9], where the null byte is placed.

Timeline

2022-01-19 - Vendor Disclosure
2022-01-19 - Vendor Patched
2022-01-26 - Public Release

CVSS2

6.4

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

NONE

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:N/I:P/A:P

CVSS3

8.2

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

LOW

Availability Impact

HIGH

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

EPSS

0.001

Percentile

33.4%

Related for TALOS-2022-1451