Talos Vulnerability Report

TALOS-2022-1661

EIP Stack Group OpENer GetAttributeList attribute_count_request out-of-bounds write vulnerability

February 23, 2023
CVE Number

CVE-2022-43604

SUMMARY

An out-of-bounds write vulnerability exists in the GetAttributeList attribute_count_request functionality of EIP Stack Group OpENer development commit 58ee13c. A specially crafted EtherNet/IP request can lead to an out-of-bounds write, potentially causing the server to crash or allow for remote code execution. An attacker can send a series of EtherNet/IP requests 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.

EIP Stack Group OpENer development commit 58ee13c

PRODUCT URLS

OpENer - https://github.com/EIPStackGroup/OpENer

CVSSv3 SCORE

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

CWE

CWE-787 - Out-of-bounds Write

DETAILS

OpENer is an EtherNet/IP stack for I/O adapter devices. It supports multiple I/O and explicit connections and includes objects and services for making EtherNet/IP-compliant products as defined in the ODVA specification.

When a GetAttributeList request is received, the number of attributes is extracted into attribute_count_request. This value is then used as the upper bound for a loop intended to iterate over each requested attribute and add the associated information to the CipMessageRouterResponse. This can be seen in the following snippet from cipcommon.c:GetAttributeList.

  ...

  CipUint attribute_count_request = GetUintFromMessage(
    &message_router_request->data);

  if(0 != attribute_count_request) {

    EipUint16 attribute_number = 0;
    CipAttributeStruct *attribute = NULL;

    AddIntToMessage(attribute_count_request, &message_router_response->message);

    for(size_t j = 0; j < attribute_count_request; j++) {                        [0] upper bound is user-controlled

      attribute_number = GetUintFromMessage(&message_router_request->data);
      attribute = GetCipAttribute(instance, attribute_number);

  ...

Within this loop the various fields associated with the attribute currently being processed are added to the response message buffer through use of the AddIntToMessage, AddSintToMessage or AddDintToMessage functions. All three of these functions are essentially helpers to convert the value in question from the host endianess to little-endian, and then write the converted data to the response buffer.

The AddIntToMessage function uses the current_message_position and used_message_length values contained within the EnipMessage to determine where within the response message buffer to write the converted data, as shown in the snippet below from endianconv.c:

/**
 * @brief converts UINT16 data from host to little endian an writes it to buffer.
 * @param data value to be written
 * @param buffer pointer where data should be written.
 */
void AddIntToMessage(const EipUint16 data,
                     ENIPMessage *const outgoing_message) {

  outgoing_message->current_message_position[0] = (unsigned char) data;
  outgoing_message->current_message_position[1] = (unsigned char) (data >> 8);
  outgoing_message->current_message_position += 2;
  outgoing_message->used_message_length += 2;
}

When a GetAttributeList request containing an attribute_count_request of size greater than 0x80 is received, the outgoing_message->current_message_position value gets incremented to an address outside of the EnipMessage buffer. Subsequent calls to AddIntToMessage then write user-supplied data outside of the EnipMessage buffer, which when specially crafted can be used to corrupt the stack and cause the process to crash, resulting in loss of communications with the server and potentially code execution.

Crash Information

Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".                                                                                                           

Breakpoint 1, 0x0000aaaaaaaab820 in HandleDataOnTcpSocket ()                                                                                                                          
(gdb) i r  
x0             0xfffff7ffd6b0      281474842482352
x1             0x0                 0
x2             0x44d1e5c2f4e5814   309937330637920276
x3             0x0                 0
x4             0x0                 0
x5             0x0                 0
x6             0x0                 0
x7             0x0                 0
x8             0xce                206
x9             0x4141001441410014  4702039572945633300
x10            0x4141001441410014  4702039572945633300
x11            0x4141001441410014  4702039572945633300
x12            0x41410014414100ff  4702039572945633535
x13            0x4141001441410014  4702039572945633300
x14            0x4141001441410014  4702039572945633300
x15            0x4141001441410014  4702039572945633300
x16            0xaaaaaaad7d50      187649984658768
x17            0xfffff7f97a28      281474842065448
x18            0x0                 0
x19            0xaaaaaaac0150      187649984561488
x20            0x0                 0
x21            0xaaaaaaaa9e20      187649984470560
x22            0x0                 0
x23            0x0                 0
x24            0x0                 0
x25            0x0                 0
x26            0x0                 0
x27            0x0                 0
x28            0x0                 0
x29            0xffffffffe900      281474976704768
x30            0xaaaaaaaab7f4      187649984477172
sp             0xffffffffe900      0xffffffffe900
pc             0xaaaaaaaab820      0xaaaaaaaab820 <HandleDataOnTcpSocket+1044>
cpsr           0x20001000          [ EL=0 SSBS C ]
fpsr           0x0                 0
fpcr           0x0                 0
pauth_dmask    0x7f000000000000    35747322042253312
pauth_cmask    0x7f000000000000    35747322042253312
(gdb) 
(gdb) 
(gdb) bt
#0  0x0000aaaaaaaab820 in HandleDataOnTcpSocket ()
#1  0x0000aaaaaaaaae6c in NetworkHandlerProcessCyclic ()
#2  0x4141001441410014 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) 
(gdb) 
(gdb) x/i $pc
=> 0xaaaaaaaab820 <HandleDataOnTcpSocket+1044>:
    bl  0xaaaaaaaa9c60 <__stack_chk_fail@plt>
(gdb) 
(gdb) 
(gdb) c
Continuing.
*** stack smashing detected ***: terminated

Program received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50      ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) 
Continuing.

Program terminated with signal SIGABRT, Aborted.
The program no longer exists.
(gdb)
(gdb)

Mitigation

Verify that the extracted attribute_count_request value times two (because it indicates number of Words, which in CIP is 16 bytes) does not exceed the number of bytes remaining in the message buffer.

TIMELINE

2022-12-06 - Vendor Disclosure
2022-12-12 - Vendor Patch Release
2023-02-23 - Public Release

Credit

Discovered by Jared Rittle of Cisco Talos.