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:
CVE-2022-2336 (disclosed by the Zero Day Initiative)
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.
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:
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.
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.
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 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.
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.
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:
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.
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.
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.
The only function that did not immediately check that the FileDirectory
object was initialized with a path is the MoveOrCopy
function.
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.
Moving a FileDirectory
occurs when the destination of the object to move is different from the one it is currently in.
Renaming a FileDirectory
occurs when the destination of the object to move is the same as the one it is currently in.
Copy occurs when the copy argument is sent when calling the MoveOrCopy
function. We did not use copy in our exploit.
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).
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.
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.
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.
Add an address space with a FileDirectory that contains two other FileDirectories to namespace 0 (Issue #2)
Perform the set path chain on one of the sub directories (Issue #3)
“Move” one directory to another
“Move” again to finish the operation
“Move” back to the original location of the folder in the address space
Add an address space with a File object that is linked to the directory we just created (Issue #2)
Perform the file set path chain to the file (Issue #4)
“Rename” the parent directory, set the name to the file name
“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.
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
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.
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.
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.
Create an SMB share on the attackers machine
Create a FileDirectory and set its path to the SMB share
Upload the companion address space
Use the directory SetPath flow to set the path of the directory.
Add File object linked to the FileDirectory
Upload the companion address space containing a file description that is linked to the FileDirectory
Use the file SetPath flow to set the path of the file \\SMB\FolderName\FileName.dll
Create FileName.dll in the SMB share and write the payload to it
Use the directory SetPath flow to move the directory containing the file back to the server machine
Use the rename path traversal to move the file to c:\system32\phoneinfo.dll
Overwrite c:\Program Files\Softing\dataFEED Secure Integration Server\ProductConfig\uacore\Opc.Ua.NodeSet2.xml with an empty xml <a>a</a>
Restart the server twice to cause a crash
After WerFault starts the code from out custom DLL should run
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
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
This vulnerability allows remote attackers to execute arbitrary code on affected installations of Softing Secure Integration Server. Although authentication is required to exploit this vulnerability, the existing authentication mechanism can be bypassed.
The specific flaw exists within the handling of OPC FileDirectory namespaces. The issue results from the lack of proper validation of user-supplied data before using it to create a server object. An attacker can leverage this in conjunction with other vulnerabilities to execute arbitrary code in the context of root.
CVSS v3: 6.6
This vulnerability allows remote attackers to create directories on affected installations of Softing Secure Integration Server. Although authentication is required to exploit this vulnerability, the existing authentication mechanism can be bypassed.
The specific flaw exists within the handling of FileDirectory OPC UA Objects. The issue results from allowing unauthorized access to the filesystem. An attacker can leverage this in conjunction with other vulnerabilities to execute arbitrary code in the context of root.
CVSS v3: 6.6
This vulnerability allows remote attackers to create arbitrary files on affected installations of Softing Secure Integration Server. Although authentication is required to exploit this vulnerability, the existing authentication mechanism can be bypassed.
The specific flaw exists within the handling of FileDirectory OPC UA Objects. The issue results from allowing unauthorized access to the filesystem. An attacker can leverage this in conjunction with other vulnerabilities to execute arbitrary code in the context of root.
CVSS v3: 4.4
This vulnerability allows remote attackers to execute arbitrary code on affected installations of Softing Secure Integration Server. Although authentication is required to exploit this vulnerability, the existing authentication mechanism can be bypassed.
The specific flaw exists within the web server. The issue results from an inconsistency in URI parsing between NGINX and application code. An attacker can leverage this in conjunction with other vulnerabilities to execute arbitrary code in the context of root.
CVSS v3: 6.6