Team82 discovered a means by which it could blind the popular Snort intrusion detection and prevention system to malicious packets.
The vulnerability, CVE-2022-20685, is an integer-overflow issue that can cause the Snort Modbus OT preprocessor to enter an infinite while-loop.
A successful exploit keeps Snort from processing new packets and generating alerts.
The vulnerability, which can be attacked remotely, has been patched by Cisco and the Snort team.
All open source Snort project releases earlier than 2.9.19 and release 184.108.40.206 are vulnerable.
Read Cisco's advisory here for commercial product patching and mitigation information.
Network analysis tools are integral to keeping networks secure by providing real-time logging and analysis of events and traffic. Snort is atop this list of analysis tools as the most popular network intrusion detection and prevention system. The open-source version of Snort still has an active community of contributors and developers, while Cisco has developed commercial versions of Snort since acquiring parent company Sourcefire in 2013.
Snort is largely used passively on the network, but it can also take action on malicious packets, making it a powerful detection tool for defenders. An attacker who could blind this tool to malicious traffic, however, could gain an important advantage over network defenders.
In this report, Team82 will demonstrate how it was able to do just that through a vulnerability, (CVE-2022-20685) we uncovered in Snort's Modbus OT preprocessor. Exploiting this vulnerability allowed us to blind Snort's ability to detect further attacks and run malicious packets on the network.
Snort's open-source network-based intrusion detection/prevention system (IDS/IPS) has the ability to perform real-time traffic analysis and packet logging on internet protocol (IP) networks. Snort performs protocol analysis, content searching, and matching based on a predefined rule set. Snort rules can be based on raw data or on Snort's built-in protocol parsers.
Rules may trigger one of three actions:
Alert rules: Generate an alert
Log rules: Alert and log the alert
Pass rules: Ignore the packet
To make the rule-writing process simpler and improve the detection capabilities, Snort comes with a set of preprocessors that are on by default and analyze and structure network traffic into objects that can be referenced later in Snort rules. Some of the preprocessors included in Snort are: ARP, DNS, SSH and some OT (operational technology) protocols, such as MODBUS / DNP3.
When writing Snort rules, one can easily use these objects—for example to check the Modbus function ID the modbus.func_id attribute can be used, instead of checking offset of the sixth byte in the packet. Here is an example for a snort rule that use modbus preprocessor attributes:
Modbus is an industrial protocol developed in 1979, first intended to transfer data over a serial line. Later it was expanded to include TCP/UDP support. The main Modbus function codes are:
While researching Snort OT preprocessors, we decided to focus on Modbus because it was one of the more complex OT preprocessors Snort supports. To understand what we found, we first need to examine the structure of the Modbus Write File Record function code.
The Write File Record Modbus command writes multiple groups of file registers to the Modbus server. A file is an organization of records. Each file may contain up to 10,000 records, addressed 0000 to 9999 decimal or 0x0000 to 0x270F.
The Write File Record Modbus command allows writing multiple groups of references. Each group is defined in a separate sub-request field that contains 7 bytes plus the data:
Reference type: 1 byte (must be specified as 6)
File number: 2 bytes
Starting record number within file: 2 bytes
Length of record to be written: 2 bytes
Data to be written: 2 bytes of data per register
The number of registers to be written, combined with all other fields in the request, must not exceed the allowable length of the Modbus protocol data unit (PDU), which is 253 bytes.
Here is a summary of the Write File Record Modbus command request:
The Modbus preprocessor handles multiple Modbus function codes. Snort uses the ModbusCheckRequestLengths function to calculate the expected size for each packet.
If we look at the function ModbusCheckRequestLengths in the file modbus_decode.c, we see a while-loop that goes over all of the groups in the packet, in order to calculate the total record lengths.
Can you already spot a potential problem? Let's go over the steps for exploitation:
The tmp_count parameter is initialized using a value from the packet->payload and represents the number of bytes remaining in the payload, according to the payload_length parameter. After tmp_count is set, we enter a while-loop with the exit condition of bytes_processed < tmp_count. Therefore, so far tmp_count = 10, which is constant and won't change during the loop. As long as bytes_processed remains less than 10, the while-loop will continue looping.
To do so, let's look at the content of the while-loop. We see that bytes_processed is affected by the record_length parameter, which consists of two bytes from the Modbus payload (Group → Record Length).
The record_length parameter is of type uint16_t, with a value from the user-controlled Modbus payload. bytes_processed is also uint16_t, and is calculated by multiplying record_length by 2 + the sub-request header size, which is 7.
However, the result of the multiplication can be more than the maximum uint16_t size, thus overflowing the value.
record_length = 0xfffe
MODBUS_FILE_RECORD_SUB_REQUEST_SIZE = 7
bytes_processed = 7 + (2 * 0xfffe)
In this example, the bytes_processed will be 0x20003, which is:
In binary, when the result is cast to uint_16t, the lower 16 bits are kept, meaning the bytes_processed will be 0000000000000011, which equals 3. If the bytes_processed is 3, we do not exit the while-loop, because 3 (bytes_processed) < 10 (tmp_count), and we enter another iteration.
Now, the new record_length will be taken from the user-controlled payload, from a specific offset that is partially affected by the bytes_processed value. Since we fully control the value of the bytes_processed using the integer-overflow bug, we can craft the payload in such a way that the newly calculated record_length will be any number we choose.
Therefore, if the next value that is read into the record_length (allegedly the next group's record length) is 0xfffb, then the bytes_processed will be calculated as follows:
bytes_processed = bytes_processed + MODBUS_FILE_RECORD_SUB_REQUEST_SIZE + 2*record_length
bytes_processed = 3 + 7 + 2*(0xfffb) = 0
So, the bytes_processed is now 0.The next time we enter the while-loop, the bytes_processed is 0 again, so we will go through steps 2 and 3 over and over, until the process is terminated by the user. This essentially keeps the process stuck in the while-loop (steps 2 & 3) "blinding" it forever. In this state, Snort will not process new packets and will not alert.
Successful exploits of vulnerabilities in network analysis tools such as Snort can have devastating impacts on enterprise and OT networks.
CVE-2022-20685, uncovered by Team82, targeted just one facet of this popular network intrusion detection and prevention system. It can be exploited remotely to create a denial-of-service condition in Snort, keeping it from processing new packets, and generating alerts.
Team82 believes network analysis tools are an under-researched area that deserves more analysis and attention, especially as OT networks are increasingly being centrally managed by IT network analysis familiar with Snort and other similar tools.
CWE-190: Integer Overflow or Wraparound
CVSSv3 score: 7.5
Description: An integer overflow vulnerability in the Snort Modbus OT preprocessor enables an attacker to remotely send a crafted packet to a vulnerable system, triggering an infinite while-loop and creating a denial-of-service condition.