refactor: prevent following symlinks

This commit is contained in:
Barış Soner Uşaklı
2025-03-10 17:59:31 -04:00
parent 76896859fa
commit e775564fc1

View File

@@ -26,13 +26,13 @@ uploadsController.get = async function (req, res, next) {
const page = parseInt(req.query.page, 10) || 1; const page = parseInt(req.query.page, 10) || 1;
let files = []; let files = [];
try { try {
files = await fs.promises.readdir(currentFolder); await checkSymLinks(req.query.dir)
files = await getFilesInFolder(currentFolder);
} catch (err) { } catch (err) {
winston.error(err.stack); winston.error(err.stack);
return next(new Error('[[error:invalid-path]]')); return next(new Error('[[error:invalid-path]]'));
} }
try { try {
files = files.filter(filename => filename !== '.gitignore');
const itemCount = files.length; const itemCount = files.length;
const start = Math.max(0, (page - 1) * itemsPerPage); const start = Math.max(0, (page - 1) * itemsPerPage);
const stop = start + itemsPerPage; const stop = start + itemsPerPage;
@@ -72,6 +72,30 @@ uploadsController.get = async function (req, res, next) {
} }
}; };
async function checkSymLinks(folder) {
let dir = path.normalize(folder || '');
while (dir.length && dir !== '.') {
const nextPath = path.join(nconf.get('upload_path'), dir);
// eslint-disable-next-line no-await-in-loop
const stat = await fs.promises.lstat(nextPath);
if (stat.isSymbolicLink()) {
throw new Error('[[invalid-path]]');
}
dir = path.dirname(dir);
}
}
async function getFilesInFolder(folder) {
const dirents = await fs.promises.readdir(folder, { withFileTypes: true });
const files = [];
for await (const dirent of dirents) {
if (!dirent.isSymbolicLink() && dirent.name !== '.gitignore') {
files.push(dirent.name);
}
}
return files;
}
function buildBreadcrumbs(currentFolder) { function buildBreadcrumbs(currentFolder) {
const crumbs = []; const crumbs = [];
const parts = currentFolder.replace(nconf.get('upload_path'), '').split(path.sep); const parts = currentFolder.replace(nconf.get('upload_path'), '').split(path.sep);
@@ -102,14 +126,14 @@ async function getFileData(currentDir, file) {
const stat = await fs.promises.stat(pathToFile); const stat = await fs.promises.stat(pathToFile);
let filesInDir = []; let filesInDir = [];
if (stat.isDirectory()) { if (stat.isDirectory()) {
filesInDir = await fs.promises.readdir(pathToFile); filesInDir = await getFilesInFolder(pathToFile);
} }
const url = `${nconf.get('upload_url') + currentDir.replace(nconf.get('upload_path'), '')}/${file}`; const url = `${nconf.get('upload_url') + currentDir.replace(nconf.get('upload_path'), '')}/${file}`;
return { return {
name: file, name: file,
path: pathToFile.replace(path.join(nconf.get('upload_path'), '/'), ''), path: pathToFile.replace(path.join(nconf.get('upload_path'), '/'), ''),
url: url, url: url,
fileCount: Math.max(0, filesInDir.length - 1), // ignore .gitignore fileCount: filesInDir.length,
size: stat.size, size: stat.size,
sizeHumanReadable: `${(stat.size / 1024).toFixed(1)}KiB`, sizeHumanReadable: `${(stat.size / 1024).toFixed(1)}KiB`,
isDirectory: stat.isDirectory(), isDirectory: stat.isDirectory(),