Team82 has made freely available a network fuzzer we used as part of our preparation for Pwn2Own Miami 2022
The fuzzer is based on the boofuzz network fuzzer
We used it to target the KepwareEX server and trigger a crash and RCE vulnerability
A network-based fuzzer is a tool used to test network protocols and software for vulnerabilities
It sends a network packet and monitors whether the payload causes a crash or abnormal behavior.
We often use fuzzers in our research to find low-hanging bugs in network protocol implementations. Occasionally, we also develop custom fuzzers or harnesses to better go after specific targets.
One such instance came around our participation at the annual Pwn2Own Miami ICS hacking competition, where the objective is to find zero-day vulnerabilities in targets specified by the contest’s organizers, the Zero Day Initiative (ZDI). In 2022, many of the targets were popular OPC-UA servers. The OPC UA protocol is a standard means of data-exchange between industrial sensors and either on-premises servers or cloud management platforms.
Our strategy was to first take a broad approach to attacking OPC UA servers for the competition, find all the low-hanging fruit, and then take a deeper dive into each implementation. A network fuzzer was the best candidate for that approach.
Today, we are making publicly and freely available the network fuzzer that we developed for Pwn2Own 2022—based on the boofuzz network fuzzer as a framework—which helped us successfully target the KepwareEX server, and trigger a crash that we were able to use to develop a remote code execution exploit against and earn $20,000 at the contest.
Let’s take a closer look at our fuzzer, which you can find on our GitHub page today.
A network-based fuzzer is a tool used to test network protocols and software for vulnerabilities by generating a large number of random or targeted network requests and monitoring the responses for unexpected behavior or crashes. It works by sending packets of data to a target system, varying the content of the packets in order to identify vulnerabilities that may be triggered by specific input. The fuzzer may use a variety of techniques to generate packet variations, such as modifying individual bytes, randomizing the order of fields within the packet, or using a predefined set of attack patterns.
A network fuzzer is quite similar to fuzzers we are more familiar with such as AFL. But instead of feeding a function within the code base with various mutations to see if those discover a new path in a code flow, or crash the system, we send a network packet (for example, some payload above the TCP/UDP layers) to the receiving interface of the server (i.e., a specific port) and monitor whether this payload causes a crash or abnormal behavior.
Because there is no instrumentation within the server’s code, we can run the same fuzzing logic against all servers regardless of their architecture. This is a huge benefit because we can shoot many fuzzers at once with relatively little development and research time. As a strategy we tend to first build network-based fuzzers before diving into poking at the target because it helps familiarize ourselves with the protocol, its implementation in the target—and we can run hundred of instances in the background while we dive into the target.
In contrast to coverage-based fuzzers such as AFL and libfuzzer, the network fuzzer has no ability to know whether the new mutation caused a new logic path to compute within the code flow. It has no way to do so because no instrumentation exists; the fuzzed environment is identical to the original. On the other hand, the same payload within the same fuzzer can be sent to multiple servers simultaneously, which was very convenient in our case.
The network fuzzer has its own advantages and disadvantages against memory fuzzers:
No source code or compilation needed
No harness needs to be added
Platform agnostic
Not coverage-based fuzzing
Needs implementation of the rules for mutation
Much slower than memory fuzzer
Our fuzzer is based on the boofuzz network fuzzer, which in turn is based on the Sulley fuzzing framework. The fuzzer is written in Python, which is very convenient since it will be very easy to write a code on top of it.
To build the fuzzer we had to implement the entire initiation of the OPC UA session. To do so we built a framework to handle sending and receiving OPC UA packets. This includes everything from creating OPC UA sessions, sending MSGs and destructing the session. From the target server perspective, we are just a “normal” client that sends a lot of funky OPC UA messages.
For Pwn2Own, we fuzzed five different OPC UA request types that we have found more promising in terms of finding vulnerabilities because they are feature rich. Within those requests, not all elements were fuzzed—recall that this is not a feedback fuzzer; all mutations are uniformly distributed, so we want to minimize the number of permutations.
Each request is sent after the OPC UA session is established, and in addition the OPC UA session is closed after any mutated packet. Yes, this is not a “fast” fuzzer.
Here the list of the OPC UA services we fuzzed and the attributes we focused:
Read Service (read_request): we are fuzzing the nodes to read
Browse Service (browse_request): we are fuzzing the browsed nodes
Browse Next Service (browse_next_request): we are fuzzing the browsed nodes
Create Subscription Service (create_subsctibtion_request): we are fuzzing the entire content
Add Nodes Service (add_nodes_request): we are fuzzing the content that describes the nodes to be added
History Read Service (history_read_request): we are fuzzing the number of nodes and the entire content of the read history request
Although no instrumentation is needed for the fuzzer to run, some preparation is needed to ensure fuzzer stability because any server that we worked with has its own implementation of the OPC-UA stack and thus can behave a bit differently from others.
For example, the Open62541 OPC-UA protocol stack (which is not in scope of this work) implementation of the stack requires the secure_chanel_id
be the same as secure_token_id
, when other implementations use different values.
Currently, our network fuzzer includes support for the following servers:
KEPServerEX (Kepware)
UA-.NETStandard (dotnetstd)
Softing OPC Server (softing)
Prosys OPC UA Simulation Server (prosys)
OPC UA C++ Demo Server (unified)
Ignition's OPC UA Server (ignition)
While the majority of OPC-UA protocol stack implementations will work out-of-the-box with the currently supported servers, users can add new support for other OPC-UA implementations. Please note that as we mainly developed this fuzzer for ourselves, the procedure of adding new support is not very developer-friendly. So what do you need to do to add new OPC-UA implementation:
raw_messages_opcua.py
- Copy raw packets from Wireshark when a regular client (e.g. UaExpert) is connecting to the server. The needed OPC-UA messages are: Hello, Open Channel, Create Session, Activate Session, and Close Session.
raw_messages_opcua.py
- Add support for your server in get_raw_open_session_messages
and get_raw_close_session_messages
functions
opcua_utils.py
- Add your new server type to the following functions: target_apps
, get_services_list
, get_sanity_payload
opcua_utils.py
- Add hardcoded ReadRequest
message (copy from Wireshark) for the new server. It will be used for sanity to check the server is functioning.
opcua_utils.py
- Edit close_session function by adding your server to target_app
.
opcua_session.py
- Some servers require specific flow upon session creation. Add the changes to create_session
function if needed.
In addition to boofuzz, we also need to install construct. We use it to build the OPC-UA payloads.
python3 -m pip install -r requirements.txt
Example: Run the fuzzer
python3 opcua_fuzzer.py \
--target_host_ip 10.10.10.10 \
--target_host_port 4897 \
--target_app_name softing \
--request_opcua_to_fuzzbrowse_request
target_host_ip
IP of the OPCUA Server
target_host_port
PORT which the OPCUA Server Listens to
target_app_name
The type of the OPCUA Server to be fuzzed, choose from kepware, dotnetstd, softing, prosys, unified, ignition
request_opcua_to_fuzz
The OPCUA Server request type to fuzz, choose from read_request
, browse_request
, browse_next_request
, create_subsctibtion_request
, add_nodes_request
, history_read_request
git clone git@github.com:claroty/opcua_network_fuzzer.git
cd opcua_network_fuzzer
python3 -m pip install -r requirements.txt
python3 ./opcua_fuzzer.py \
--ti IP \
--tp PORT \
--ta [kepware, dotnetstd, softing, prosys, unified, ignition] \
--r [read_request, browse_request, browse_next_request, create_subsctibtion_request, add_nodes_request, history_read_request]
Sometimes before running the fuzzer you want to ensure that the OPC UA session is correctly created and terminated. To send only one mutation with \x00
payload above ReadRequest
, change IS_TEST_RUN
variable to True
When the application crashes, the fuzzer will stop because no new connections could be made with the server. The last 1000 sent packets are saved in a sqlite
database in the boofuzz-results
directory within this repository. The status of the fuzzer can be monitored here: http://localhost:26000/
To ensure fuzzer stability, validate that the target server is configured to allow many concurrently opened sessions (around 1000)
When fuzzing, ensure that the fuzzed service doesn't restart automatically after a crash.
Team82’s network fuzzer was vital to our discovery of zero-day vulnerabilities in the KepwareEX server, which were reported during Pwn2Own Miami 2022 and patched by the vendor, PTC. Fuzzers play an important role in our work, and we’re happy to share it today via our GitHub page.
CWE-345 INSUFFICIENT VERIFICATION OF DATA AUTHENTICITY:
In 2N Access Commander versions 3.1.1.2 and prior, a local attacker can escalate their privileges in the system which could allow for arbitrary code execution with root permissions.
Update to Access Commander version 3.2.
CVSS v3: 4.7
CWE-345 INSUFFICIENT VERIFICATION OF DATA AUTHENTICITY:
In 2N Access Commander versions 3.1.1.2 and prior, an Insufficient Verification of Data Authenticity vulnerability could allow an attacker to escalate their privileges and gain root access to the system.
Update to Access Commander version 3.2.
CVSS v3: 6.3
CWE-22 IMPROPER LIMITATION OF A PATHNAME TO A RESTRICTED DIRECTORY ('PATH TRAVERSAL'):
In 2N Access Commander versions 3.1.1.2 and prior, a Path Traversal vulnerability could allow an attacker to write files on the filesystem to achieve arbitrary remote code execution.
Update to Access Commander version 3.2.
CVSS v3: 7.2
CWE-290: AUTHENTICATION BYPASS BY SPOOFING
Snap One OVRC cloud uses the MAC address as an identifier to provide information when requested. An attacker can impersonate other devices by supplying enumerated MAC addresses and receive sensitive information about the device.
Read more: "The Problem with IoT Cloud Connectivity and How it Exposed All OvrC Devices to Hijacking"
CVSS v3: 7.5
CWE-306: MISSING AUTHENTICATION FOR CRITICAL FUNCTION
A vulnerability exists in Snap One OVRC cloud where an attacker can impersonate a Hub device and send requests to claim and unclaimed devices. The attacker only needs to provide the MAC address of the targeted device and can make a request to unclaim it from its original connection and make a request to claim it.
OvrC Pro: All versions prior to 7.3 are affected.
Read more: "The Problem with IoT Cloud Connectivity and How it Exposed All OvrC Devices to Hijacking"
CVSS v3: 9.1