Team82 Logo Claroty

Team82 Research

Team82 Releases Homegrown OPC UA Network Fuzzer Based on boofuzz

Vera Mens
/ March 30th, 2023
Team82 Releases Homegrown OPC UA Network Fuzzer Based on boofuzz
Demo of our fuzzer finding CVE-2022-2848 on PTC Kepware KEPServerEX out-of-the-box

Executive Summary

  • 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.

Table of Contents

  1. What is a Network Fuzzer?

  2. Network Fuzzer: Pros and Cons

  3. Technical Details

  4. Run the Fuzzer

  5. How to Use It

  6. Run Sanity Payload

  7. Fuzzing Results

  8. Important Notes

What is a Network Fuzzer?

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.

OPC-UA server fuzzed requests
Five different requests are fuzzed against an OPC-UA server.

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.

Network Fuzzer: Pros and Cons 

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

Technical Details

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.

Fuzzed Requests

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:

Fuzzed Servers

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:

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:

  1. - 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.

  2. - Add support for your server in get_raw_open_session_messages and get_raw_close_session_messages functions 

  3. - Add your new server type to the following functions: target_apps, get_services_list, get_sanity_payload

  4. - Add hardcoded ReadRequest message (copy from Wireshark) for the new server. It will be used for sanity to check the server is functioning.

  5. - Edit close_session function by adding your server to target_app.

  6. - Some servers require specific flow upon session creation. Add the changes to create_session function if needed.

Run the Fuzzer

Install Dependencies

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 \
--target_host_ip \
--target_host_port 4897 \
--target_app_name softing \
  • 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

How to Use It

git clone
cd opcua_network_fuzzer
python3 -m pip install -r requirements.txt
python3 ./ \
--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]

Run Sanity Payload

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

Fuzzing Results

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/

Important Notes

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

Stay in the know

Get the Team82 Newsletter


Recent Vulnerability Disclosures

OPC-UA server fuzzed requests
LinkedIn Twitter YouTube Facebook