Team82 Logo Claroty
Return to Blog

Fuzzing and PR’ing: How We Found Bugs in a Popular Third-Party EtherNet/IP Protocol Stack

/
Bugs discovered in Third-Party EtherNet/IP Protocol Stack

The OpENer EtherNet/IP stack implements the familiar ENIP and CIP protocols that run inside numerous commercial products for use across the industrial domain. Its popularity among the major SCADA vendors that use it puts a premium on finding security vulnerabilities before threat actors can exploit them.

In that context, fuzz-testing code is the most straightforward and automated way to find coding errors and potentially critical flaws. Integrating a fuzzer, however, can be a challenge in an open source implementation requiring substantial development efforts, for example to ensure fuzzed input is correctly dissected.

The Claroty Research Team, as part of its further research into the security of the OpENer stack, today announces that it has added the necessary infrastructure to incorporate the popular AFL fuzzer into the OpENer EtherNet/IP stack. Moving forward, Claroty's work has simplified the efforts of any commercial entity or researcher using OpENer in their projects to invoke a fuzzer and test the security and robustness of their implementation.

Bugs discovered in Third-Party EtherNet/IP Protocol Stack
A peek at the AFL fuzzer in action uncovering bugs in the OpENer EtherNet/IP stack.

Fuzz-testing tools are automated programs that use invalid or random data as structured software inputs, and monitor outputs for crashes or exploitable vulnerabilities. In this case, we modified OpENer and added all the necessary boilerplate code in a way that would help integrate the popular AFL (American Fuzzy Lop) fuzzer, into the OpENer stack. AFL uses runtime guided techniques to create input for the tested program. We did our best to add relevant documentation to the project so the onboarding process will be simple for anyone, including folks with no cybersecurity background.

The inclusion of our integration code in the OpENer stack relieves anyone using the project from the significant coding normally required to modify source code and write scripts necessary to integrate a fuzzer. Anyone today who wants to run it, can compile the updated OpENer EtherNet/IP stack code and immediately start fuzzing it without any changes.

This is not Claroty's first open-source contribution. We invite you to check out Claroty Open Source, which was announced in January. This initiative allows Claroty to contribute to the shared pool of knowledge and tools in the open-source community. As part of this initiative, we will be periodically releasing code packages we created to solve some of the unique challenges we've faced in the development of our products. The first two packages include JWThenticator, a key-based authentication package for cloud services applications, and NETunnel, a code package that enables network tunneling over HTTPS connections. Last July, Claroty also shared AccessDB Parser, a testing, fuzzing, and reverse engineering tool that quickly parses Microsoft AccessDB files.

Claroty also used AFL in its research into the OpENer stack; we uncovered vulnerabilities that Claroty and the EIPStackGroup—the maintainers of the OpENer stack—are disclosing today. ICS-CERT has also published an advisory today warning users of these issues. OpENer commits and versions prior to Feb. 10 are affected. All of the vulnerabilities disclosed today have been addressed and EIPStackGroup urges users to apply the latest commits.

Technical Details

Claroty's research into the OpENer stack uncovered five vulnerabilities that, depending on the architecture of the targeted device, could lead to denial-of-service conditions, memory leaks from the stack, and remote code execution. An attacker would only need to send crafted ENIP/CIP packets to the device in order to exploit these vulnerabilities.

One of the vulnerabilities privately reported by Claroty to EIPStackGroup was also disclosed by Cisco Talos, and reported publicly on Dec. 2, 2020. The vulnerability, CVE-2020-13556, is an out-of-bounds write vulnerability in the ENIP server; a sequence of crafted network requests would trigger the vulnerability, Cisco Talos said in its advisory.

What follows are technical details on some of the vulnerabilities uncovered and disclosed in October 2020 by Claroty:

CVE-2021-27478: Incorrect Conversion between Numeric Types (CWE-681)

A forward-open request is a CIP request that opens a session to a specific CIP path and allows a client to communicate with an endpoint over the same CIP session without sending the entire CIP path again in every request. When sending a forward-open request, the client must specify the connection CIP path to establish communication.

We discovered a bug in the forward-open CIP connection path parsing mechanism. The ParesConnectionPath function extracts the length of the connection path from the packet and then parses all the following bytes of the connection path. However, we found that the connection path length is extracted from the packet using the GetSintFromMessage function even though lengths should always be treated as unsigned integers.

Next, OpENer converts the extracted signed value directly to size_t. This is a bad casting because attackers can send a big 1 byte length such as 0xff that will be translated by the GetSintFromMessage function to -1 and represented by 0xffffffff.. (signed). When converted to size_t, the big size will remain as 0xffffffff.. however, this time as unsigned which means a huge CIP connection path length.

Bad casting in the ParseConnectionPath function
Bad casting in the ParseConnectionPath function at src/cip/cipconnectionmanager.c#L1133.

In order to show how dangerous it can be, we chose to create a proof-of-concept that bypasses all the checks and reaches all the way down to the final while loop that parses the remaining CIP connection path bytes until the initial CIP connection path length is 0.

ParseConnectionPath function
Our goal was to reach all the way down to this part of the ParseConnectionPath function.

To do this, we first had to bypass two checks at the beginning of the ParseConnectionPath function. These checks verify that the CIP connection path length provided in the packet matches the amount of bytes provided within the packet.

path size request

We added a couple of prints in the code and re-compiled the OpENer project. Then we sent a CIP Forward-Open request with a CIP connection path of 0xff. Due to the bad casting, this was translated to a length of 18446744073709551615 (assuming a 64-bit system). Since the header_length is constant and equals 36, we had to make sure that the request path size will be 34 and the equation will be satisfied:

header_length + remaming_path*2 = request_path_size 36 + -1*2 = request_path_size

So, the request_path_size must be equal 34 to bypass these checks, shown below.

remaining path

However, 34 bytes means the size of the complete ENIP payload will not match the reported size in the packet. We poked in the code and discovered that if we are to supply more than two ENIP items, then we will be able to bypass this check too.

CreateCommonPacketFormatStructure
The check in CreateCommonPacketFormatStructure allows packets which report wrong sizes to be dissected.

Next, we had a couple of more obstacles to bypass, but they were relatively easy because they only required some specific bytes to be added to the final payload. This is the final packet we sent in order to force OpENer to dissect a Forward-Open CIP request with a huge CIP connection path length of 18446744073709551615 bytes.

Item count and Length

CVE-2020-13556: Out-of-Bounds Write (CWE-787) (Disclosed by Cisco Talos in December 2020)

ENIP packets contain a list of items. Upon receiving an ENIP packet, OpENer will try to extract the number of items (item count) embedded in the packet. However, besides an assert statement there are no checks on the amount of supplied ENIP item count. And so, on production systems where an OpENer is compiled in release mode, attackers will be able to provide a large item count number that will later cause a stack buffer overflow.

CreateCommonPacketFormatStructure function
CreateCommonPacketFormatStructure function as can be seen here.

The provided common_packet_format_data is a buffer in the length of 512 bytes as can be seen here:

If the user provides a large buffer with a large enough item count, the payload from the packet will overwrite values outside the allocated memory buffer. This can lead to remote code execution if the supplied payload is crafted carefully. Here is the loop that extracts bytes from the packet and writes them into the allocated buffer array.

common packet format

CVE-2021-27482: Out-of-bounds Read (CWE-125)

As stated above, the common_packet_format_data variable points to a buffer of 512 bytes in length. In addition, there are no checks on the bytes read from the provided packet. This means that if we send a packet of 512 bytes, and force OpENer to process 3 items count, the reading bytes will be out-of-bound and we will be able to read random bytes from the stack.

To leak these bytes back, we used a special ENIP item type kCipItemIdSocketAddressInfoOriginatorToTarget. This item uses sin_port, sin_addr attributes included in the packet. However, since we provided the maximum allowed packet size to fit in the allocated 512 bytes long buffer, we actually forced OpENer to read out-of-bounds and copy random bytes from the stack to sin_port, sin_addr and include them in the response back. Here we can see how OpENer copies these bytes to the output buffer before sending the response back to us.

Here is the final response packet with the leaked bytes:

Leaked bytes from the stack

The Vulnerabilities

CVE-2020-13556

CWE-787 Out-of-Bounds Write

CVSS v3 score: 9.8

(Disclosed by Cisco Talos) A specially crafted series of network requests can lead to remote code execution.

CVE-2021-27478

CWE-681 Incorrect Conversion Between Numeric Types

CVSS v3 score: 8.2

An attacker can use specially crafted packets to vulnerable devices to cause a denial-of-service condition.

CVE-2021-27482

CWE-125 Out-of-Bounds Read

CVSS v3 score: 7.5

An attacker can use specially crafted packets to vulnerable devices to read arbitrary data.

CVE-2021-27500

CWE-617 Reachable Assertion

CVSS v3 score: 7.5

An attacker sending crafted packets to vulnerable devices may result in denial-of-service conditions.

CVE-2021-27498

CWE-617 Reachable Assertion

CVSS v3 score: 7.5

An attacker sending crafted packets to vulnerable devices may result in denial-of-service conditions.

Stay in the know Get the Team82 Newsletter
Related Vulnerability Disclosures
Claroty
LinkedIn Twitter YouTube Facebook