CVE-2022-43604
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.
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
OpENer - https://github.com/EIPStackGroup/OpENer
10.0 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CWE-787 - Out-of-bounds Write
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.
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)
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.
2022-12-06 - Vendor Disclosure
2022-12-12 - Vendor Patch Release
2023-02-23 - Public Release
Discovered by Jared Rittle of Cisco Talos.