Team82 Logo Claroty

Team82 Research

OPC UA Deep Dive Series (Part 9): Chaining Vulnerabilities to Exploit Softing OPC UA Integration Server

Uri Katz
/ January 31st, 2024

Executive Summary 

  • Team82’s OPC UA Deep Dive Series continues with research into OPC UA-based integration servers

  • During Pwn2Own ICS 2023 in Miami, we used five vulnerabilities in Softing Industrial Automation’s Secure Integration Server (SIS)—four of which we uncovered and disclosed—and demonstrated, using a very complex chain, how to achieve remote code execution on the server. The five vulnerabilities in our chain are:

  • Softing has patched these vulnerabilities in updated versions of Secure Integration Server, Softing edgeConnector, and Softing edgeAggregator.

  • Softing issued multiple advisories including SYT-2022-6, SYT-2023-9, and SYT-2023-8.

What is Softing Secure Integration Server?

Team82’s ongoing OPC UA Deep Dive Series has taken you on a journey exploring the depths of the protocol and its core components, a peek behind the curtain at our research methodology and exploit framework, and some possible real-world attacks against OPC UA clients

In part nine, we’re going to look at remote code execution (RCE) attacks against OPC UA integration servers. We uncovered four new vulnerabilities during our research of the Softing Secure Integration Server, which we were able to chain along with a fifth vulnerability found by the Zero Day Initiative in order to achieve full RCE on the server. The vulnerabilities range from bypasses of security features and programming limitations meant to restrict access to the server, to others that allowed us to read and write arbitrary files on the server. 

Below is a demonstration of our proof-of-concept exploit against a vulnerable target which we successfully demonstrated at Pwn2Own ICS 2023:

Understanding OPC UA Filesystem Types

In the OPC UA protocol, there are two important types handling file system directories and files.

  • OPC UA FileDirectory type is a data type in the OPC UA standard that represents a directory containing FileType objects.

  • OPC UA FileType type is a data type that represents a file on the disk.

File and directory OPC UA objects are linked to actual files and directories on the file system. FileDirectory/FileType objects are used to access and manage files and directories remotely and enable integration of file systems with OPC UA applications, below.

It’s important to distinguish between OPC UA FileType/FileDirectory objects in memory to the actual files/directories they point to on the disk. For example, we can create a FileType OPC UA object that will live in memory as part of the OPC UA nodes, but it will not point to an actual file on the disk. Essentially, it’s just an object and a potential interface to control a file on the disk.

OPC UA FileDirectory Type

The OPC UA FileDirectory type includes four methods:

  • CreateDirectory: Creates a directory under the current FileDirectory

  • CreateFile: Creates a file under the current FileDirectory

  • Delete: Deletes the current FileDirectory or one that is linked to it

  • MoveOrCopy: Moves or copies a file or directory organized by this FileDirectory

All of these functions are responsible for moving and reorganizing OPC objects and the respective associated files and directories on the filesystem.

An OPC UA node tree that defines the File/Directory objects.

Having full control over OPC UA FileDirectory objects can result in code execution in most cases. Creating a file in any directory and controlling its name and content is usually enough to write a binary, or overwrite a configuration file in order to achieve code execution. For this reason most servers do not implement the FileDirectory type and/or limit its use extensively.

Softing’s implementation of FileDirectory

Softing implements a handler for FileDirectory functions by default in its server even though the server does not include any FileDirectory objects. This poses a few limitations to using the FileDirectory:

  • The server does not include FileDirectory objects by default

  • FileDirectory objects usually require that the link between the OPC UA object and the filesystem path would be done by the server code. This means the server using the Softing SDK needs to implement the connection “hard-coded” in their code.

  • Files that are under the FileDirectory must be inside the directory and cannot be standalone files.

A note from Softing’s documentation, below, says that setting the path of a FileType object (this is also true for FileDirectories) is not possible from the NodesetXml2 XML and can be only set using the setFilePath method.

When using a File object via Nodeset2 XML import, an application has to link the file instance via setFilePath to a physical file. Otherwise, accessing the file is not possible. 


Creating FileDirectories in Secure Integration Server

Secure Integration Server has a feature that lets users upload a custom address space. A namespace in OPC UA is like a container for node IDs. There is the predefined namespace with index 0 from the OPC Foundation, and many other namespaces. Each namespace belongs to a specific OPC UA specification, and every OPC UA specification can define its own node IDs. To make sure a specific ID uniquely identifies a specific node within a namespace, you need to indicate the namespace ID.

The address space is used for expanding the server and the nodes it represents. Objects created from importing an XML address space should be created in their own namespace.

Setting the OPC-UA address space in Secure Integration Server.

Softing Secure Integration Server Vulnerabilities

While researching OPC UA, we discovered multiple flaws that when chained enabled us to achieve pre-auth remote code execution. The chain is a bit complex and requires an intimate knowledge of OPC UA internals. So let’s dive in:

#1: CVE-2022-2336: Hardcoded credentials to the web interface (port 8099)

The first vulnerability in our chain had been previously disclosed by Pedro Ribeiro, Radek Domanski from Flashback Team; Softing Secure Integration server, edgeConnector, and edgeAggregator shipped with default admin/admin credentials. These weak credentials allowed users access to the server’s web interface and perform admin functions. Softing addressed these flaws in V1.30.

#2: CVE-2023-39478: Bypass of Separation Between OPC Namespaces

Creating a FileDirectory object in namespace 0: Namespace 0 is the default root namespace for the OPC server that was created by the OPC Foundation. It should contain all default OPC UA types and the main server object used for server configuration and tracking. 

Using the Load Mapped/Companion Address Space functionality of the server web interface, we were able to add a namespace with FileDirectory objects in namespace 1. But since the FileDirectory methods are defined in namespace 0, we were not able to reference the function calls from namespace 1 to the relevant function in namespace 0.

However, after looking at the server implementation, we discovered that even though the server is not supposed to accept mapped address spaces with objects in namespace 0 it does create them. This poses a security risk because it should contain pre-defined and known OPC UA objects.

A demonstration how we modified namespace 0 with FileDirectory objects.

Set Directory Path

After loading our custom address space we can see the FileDirectory objects created in namespace 0 but calling any one of the FileDirectory methods fails immediately because the object is not linked to any filesystem path. We started looking at the implementation of the FileDirectory methods in order to try and find a way to trigger the SetPath method.

OTServerFileDirectory::createFile: File creation fails because there is no associated file system path.

The only function that did not immediately check that the FileDirectory object was initialized with a path is the MoveOrCopy function.

#3: CVE-2023-39479: Bypass of Limitations on Assignment of Directory Path to File Directory OPC UA Objects

MoveOrCopy Method

The MoveOrCopy function can be divided into three different logical operations:

  • Copy 

  • Move

  • Rename

These operations are quite different if they are performed on a FileType object or a FileDirectory. We will start by looking at directories.

Directory Move

Moving a FileDirectory occurs when the destination of the object to move is different from the one it is currently in.

Rename Directory

Renaming a FileDirectory occurs when the destination of the object to move is the same as the one it is currently in.

Copy Directory

Copy occurs when the copy argument is sent when calling the MoveOrCopy function. We did not use copy in our exploit.

Setting the Path Using MoveOrCopy

At this point we had an OPC UA FileDirectory object in the OPC UA nodes tree, but these objects were not linked to any actual directory on the disk. We needed to find a way to use the SetPath method in order to create the link between the abstract object and a real directory on the file system disk.

We discovered a specific code flow that lets us create a directory on the remote server and also set the filesystem path of a FileDirectory object. In order to do that, we need to load a specific mapped OPC-UA address space containing objects in a specific hierarchy:

  • FileDirectory containing two other FileDirectories: In this configuration we can use the “move” primitive.

  • The target FileDirectory we want to set the path to ; it cannot contain any file objects because move/rename will later fail on these inner files.

  • The path we want to set does not currently exist in the remote server filesystem.

In this configuration, moving one FileDirectory into the other one and giving it a full path as the name will go through most of the MoveOrCopy function, create the directory, and reach the SetPath function. The MoveORCopy will fail after that, but the error handling does not revert the SetPath.

At this point we have a partially valid FileDirectory object; it has a valid path but the references to it and other nodes are not set because the MoveOrCopy failed. Since the object has a valid path, calling the MoveOrCopy function again moving the same folder will finish successfully. And so we were able to bypass the limitation to set the OPC UA object path and link it to the actual path on the disk. (Note that the path in the second MoveOrCopy needs to be different from the first one).

#4: CVE-2023-39480: Bypass of Limitations on Assignment of File Path to File OPC UA Objects

Creating Files

Our first idea was to do the same flow as we did for FileDirectories but for a FileType object. However, we discovered that file objects are handled differently in the MoveOrCopy function. 

Every code flow we found leads to one of these Windows function:

  • MoveFileExW

  • CopyFileW

Both functions do not accept NULL as a path parameter so both fail at the WinAPI level. This is also the reason the FileDirectories cannot contain files in the SetPath flow. The MoveOrCopy of a directory tries to move the files organized by the FileDirectory and if this fails, we don’t reach the SetPath function.

Our second idea was to use the CreateFile method after setting the path to a FileDirectory object. This should create a file in the path we set previously. An unrelated issue prevented us from using this function; when the file is created the server creates a NodeID that identifies the file object. NodeIDs represent an address in the OPC server and can be represented in multiple different ways including String, Guid, and even Numeric types. The NodeID that the server creates is a GUID, but the server refuses to add NodeIDs that are not numeric to NameSpace 0. This unrelated issue prevents us from using the CreateFile method.


The solution: First set a path on a FileDirectory object using Issue #3, then use Issue #2 to add another address space to the server containing a file object that is linked to the FileDirectory object we set the path to.

NodesetXml for a file object with ParentNodeId linked to FileDirectory

Now we have a FileDirectory with a valid path that contains a File object with no path. We discovered a specific flow where we can set the file path as well. 

Using the “rename” primitive with renaming the directory containing the file will call SetPath both on the FileDirectory object and on the File object it contains. 

Currently, our OPC UA file object has no path linked to a real filesystem file. This means that when we rename the directory object, a recursive process will start to rename all the file objects under the directory object but since no path is linked to the file object, thefFile object will receive the same path as the parent directory object. 

Renaming the directory once again will rename the directory object but keep the file object name intact because this time it has a path. 

So if we break it down אhe first rename path will determine the file name and the second determines the folder that contains the file. While this double rename does not create the file, it does create a valid OPC UA File object linked to a real path on the file system. 

Creating a valid FileDirectory with a File: Full Flow

Here is a short summary of Issue #2, #3, and #4 that we used to create an OPC UA FileType object linked to a real file on the filesystem disk.

  1. Add an address space with a FileDirectory that contains two other FileDirectories to namespace 0 (Issue #2)

    1. Perform the set path chain on one of the sub directories (Issue #3)

  2. “Move” one directory to another

  3. “Move” again to finish the operation

  4. “Move” back to the original location of the folder in the address space

  5. Add an address space with a File object that is linked to the directory we just created (Issue #2)

    1. Perform the file set path chain to the file (Issue #4)

  6. “Rename” the parent directory, set the name to the file name

  7. “Rename” the parent directory, set the name to the path of the folder containing the file

We have an OPC UA File object linked to a real file, however, the file is initialized as non-writable so we cannot create the file or write to an existing one.

The solution: Since most Windows WinAPI file-handling functions (e.g. CreateFileW) accept network directories, we decided to try and create the files and directories in a remote SMB share that we can control. Setting the path to a network share in our control allows us to write the file data after it is created and, using the “move” primitive, move the FileDirectory back to server “C:\” folder.

OPC UA FileDirectory→FileType rename directory traversal

Rename path traversal

Until now we have only discussed how we can create a new directory that contains a file. In order to make this set of vulnerabilities more powerful, we wanted to ditch the folder and be able to create a file that is not contained inside our new folder. While trying to do this we discovered that after setting the file name we can rename the file object to any name we want. The rename does not check for path traversal so naming the OPC UA FileType object ..\FileName.txt would result in moving the file:

From: c:\FolderName\FileName.txt

To: c:\FileName.txt

From File Write to Code Execution

There are many ways to use a file write primitive to trigger code execution, especially with SYSTEM privileges. We decided to write a DLL file called phoneinfo.dll to system32 Windows directory and crash the server. In Windows, when an application crashes, the WerFault executable runs and collects crash info. 

The werfault.exe is found in c:\Windows\System32. When WerFault starts running, it tries to load phoneinfo.dll and first looks in the directory it runs from (system32). By default, the DLL does not exist in system32 so writing our custom DLL to that location and crashing the server application will execute our code.

CVE-2023-39481 : Arbitrary file write using /runtime/uacore/nodesetxml

Crashing the Server

In order to crash the server we used a new vulnerability we discovered in the web server that lets us write any file with any name anywhere on the server as long as the content of the file is a valid XML.

In order to understand this vulnerability we first need to understand the structure of the Softing web server. There are two main processes listening on two different ports:

  • Port 8099: The main HTTP port handled by nginx web server (runs as service)

  • Port 9000: Softing application server, implemented with FastCGI interface and receives data from NGINX after the HTTP packet was first received there. 

    • Note: By default this should only be listening on 127.0.0.1, but for some reason (bug/misconfiguration) this service is listening on 0.0.0.0 and allows anyone to communicate with it remotely using the FastCGI protocol.

The initial parsing done by nginx ensures that the URI starts with /runtime before passing it on to the application handling on port 9000.

Arbitrary XML Write By Abusing Path Traversal Vulnerability

The /runtime/uacore/nodesetxml/ URI route enables users to read or write an XML nodeset file specified in the path.

 In order to  read NodesetXml2.xml you need to “GET” /runtime/uacore/nodesetxml/NodesetXml2.xml 

In order to write NodesetXml2.xml you need to “POST” /runtime/uacore/nodesetxml/NodesetXml2.xml 

We discovered that writing a nodeset file that starts with ..%2f will traverse one directory up and read or write the file there.

The problem: nginx parses the url resolving the %2f and calculates the full URI. If the URI contains more than two ..%2f the resulting URI would not contain /runtime and will be invalid.

The solution: In order to traverse more that two directories and write the file to any location we can add a # (hashtag) after the first traversal. In a URL, the "#" symbol is commonly used to indicate an anchor or fragment identifier. It is used to jump to a specific part of a web page, similar to how a named anchor works in HTML. For example:

https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#HTTP_data_exchange

Will take us to:

Page: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol

Anchor: HTTP_data_exchange


Since this is not a part of the URI itself, NGINX stops the parsing of the URL and does not traverse further. When the URI reaches the application in is interpreted as a filesystem path and not as a URI so the # does not stop the parsing.

Adding ..%2fAAAA#..%2f will go into the directory AAAA#, then back. Adding subsequent ..%2f will keep traversing back the directories. 

Using the file write primitive we can overwrite or create any file on the server. We found that overwriting c:\Program Files\Softing\dataFEED Secure Integration Server\ProductConfig\uacore\Opc.Ua.NodeSet2.xml and calling the restart server twice results in a full server crash.

Putting it Together: PoC RCE

  1. Create an SMB share on the attackers machine

  2. Create a FileDirectory and set its path to the SMB share

    1. Upload the companion address space

    2. Use the directory SetPath flow to set the path of the directory.

  3. Add File object linked to the FileDirectory

    1. Upload the companion address space containing a file description that is linked to the FileDirectory

    2. Use the file SetPath flow to set the path of the file \\SMB\FolderName\FileName.dll

  4. Create FileName.dll in the SMB share and write the payload to it

  5. Use  the directory SetPath flow to move the directory containing the file back to the server machine

  6. Use the rename path traversal to move the file to c:\system32\phoneinfo.dll 

  7. Overwrite c:\Program Files\Softing\dataFEED Secure Integration Server\ProductConfig\uacore\Opc.Ua.NodeSet2.xml with an empty xml <a>a</a> 

  8. Restart the server twice to cause a crash

  9. After WerFault starts the code from out custom DLL should run

Key Takeaways

In part nine of Team82’s OPC UA Deep Dive Series, we disclosed a chain of vulnerabilities in Softing’s Secure Integration Server demonstrating real-world attacks against OPC UA integration servers. This is a key data integration layer for automation environments where data from controllers is fed through this layer to IT and enterprise applications that analyze this information to improve process efficiency. 

Softing is a leader in this space, and has patched all of the vulnerabilities disclosed by Team82 and the Zero Day Initiative that affect not only the SIS server, but also edgeConnector and edgeAggregator. 

#1: CVE-2022-2336: Hardcoded credentials to the web interface (port 8099)

#2: CVE-2023-39478: Bypass of Separation Between OPC Namespaces Creating a FileDirectory object in namespace 0

#3: CVE-2023-39479: Bypass of Limitations on Assignment of Directory Path to File Directory OPC UA Objects

#4: CVE-2023-39480: Bypass of Limitations on Assignment of File Path to File OPC UA Objects

#5: CVE-2023-39481: Arbitrary file write using /runtime/uacore/nodesetxml

More from the OPC UA Deep Dive Series

OPC UA Deep Dive (Part 1): History of the OPC UA Protocol
OPC UA Deep Dive (Part 2): What is OPC UA?
OPC UA Deep Dive (Part 3): Exploring the OPC UA Protocol
OPC UA Deep Dive Series (Part 4): Targeting Core OPC UA Components
OPC UA Deep Dive Series (Part 5): Inside Team82’s Research Methodology

OPC UA Deep Dive Series (Part 6): A One-of-a-Kind Exploit Framework
OPC UA Deep Dive Series (Part 7): Practical Denial of Service Attacks

OPC UA Deep Dive Series (Part 8): Gaining Client-Side Remote Code Execution

Stay in the know

Get the Team82 Newsletter

Related Vulnerability Disclosures

Claroty
LinkedIn Twitter YouTube Facebook