mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 11:02:33 -05:00
The on-disk name of an upload starts with its file id, which the client sends in opts.meta.fileId. Attachments validated it (UploadPathBleed); avatars - the same Meteor-Files pipeline - used it verbatim, so an avatar upload with a fileId like ../../x wrote outside the avatar store, and Avatars.onBeforeUpload did not require a signed-in user. Both collections now share one rule (models/lib/uploadFileId.js): an id that does not look like WeKan's own is replaced with a fresh one and the attempt recorded; avatars need a signed-in uploader. The negative test fails on any namingFunction in the tree without the rule. Thanks to xet7 !
31 lines
1.3 KiB
JavaScript
31 lines
1.3 KiB
JavaScript
'use strict';
|
|
// The on-disk name of an uploaded file starts with its file id, which the
|
|
// CLIENT supplies (opts.meta.fileId) during an upload. Used verbatim, a value
|
|
// like "../../x" is a path traversal: an arbitrary file write on the server.
|
|
// Attachments checked it (UploadPathBleed); avatars, the same Meteor-Files
|
|
// pipeline, did not until 2026-10-02. One rule for both: it must look like the
|
|
// ids WeKan generates, or a fresh one is used and the attempt recorded.
|
|
// Pure apart from the record: tests/uploadFileId.test.cjs.
|
|
const SAFE_FILE_ID = /^[a-zA-Z0-9_-]{1,40}$/;
|
|
|
|
function isSafeUploadFileId(fileId) {
|
|
return typeof fileId === 'string' && SAFE_FILE_ID.test(fileId);
|
|
}
|
|
|
|
// The id to use: the client's when it is safe, otherwise `fresh()`.
|
|
function safeUploadFileId(fileId, { source, fresh, isServer }) {
|
|
if (isSafeUploadFileId(fileId)) return fileId;
|
|
if (isServer) {
|
|
try {
|
|
// eslint-disable-next-line global-require
|
|
require('/server/lib/securityLog').record({
|
|
key: 'authz.upload-path', action: 'blocked', source,
|
|
detail: 'rejected a malformed/unsafe fileId, generated a fresh one instead',
|
|
});
|
|
} catch (e) { /* logging must never break the upload */ }
|
|
}
|
|
return fresh();
|
|
}
|
|
|
|
module.exports = { SAFE_FILE_ID, isSafeUploadFileId, safeUploadFileId };
|