refactor: Add new Parse Server option fileUpload.fileExtensions to restrict file upload by file extension (#8539)

This commit is contained in:
Manuel
2023-05-21 01:31:52 +02:00
committed by GitHub
parent db4c214eac
commit a27482c57e
6 changed files with 209 additions and 25 deletions

View File

@@ -465,6 +465,11 @@ export class Config {
} else if (typeof fileUpload.enableForAuthenticatedUser !== 'boolean') {
throw 'fileUpload.enableForAuthenticatedUser must be a boolean value.';
}
if (fileUpload.fileExtensions === undefined) {
fileUpload.fileExtensions = FileUploadOptions.fileExtensions.default;
} else if (!Array.isArray(fileUpload.fileExtensions)) {
throw 'fileUpload.fileExtensions must be an array.';
}
}
static validateIps(field, masterKeyIps) {

View File

@@ -975,6 +975,13 @@ module.exports.FileUploadOptions = {
action: parsers.booleanParser,
default: false,
},
fileExtensions: {
env: 'PARSE_SERVER_FILE_UPLOAD_FILE_EXTENSIONS',
help:
"Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^[^hH][^tT][^mM][^lL]?$` which allows any file extension except HTML files.",
action: parsers.arrayParser,
default: ['^[^hH][^tT][^mM][^lL]?$'],
},
};
module.exports.DatabaseOptions = {
enableSchemaHooks: {

View File

@@ -222,6 +222,7 @@
* @property {Boolean} enableForAnonymousUser Is true if file upload should be allowed for anonymous users.
* @property {Boolean} enableForAuthenticatedUser Is true if file upload should be allowed for authenticated users.
* @property {Boolean} enableForPublic Is true if file upload should be allowed for anyone, regardless of user authentication.
* @property {String[]} fileExtensions Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^[^hH][^tT][^mM][^lL]?$` which allows any file extension except HTML files.
*/
/**

View File

@@ -540,6 +540,9 @@ export interface PasswordPolicyOptions {
}
export interface FileUploadOptions {
/* Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^[^hH][^tT][^mM][^lL]?$` which allows any file extension except HTML files.
:DEFAULT: ["^[^hH][^tT][^mM][^lL]?$"] */
fileExtensions: ?(string[]);
/* Is true if file upload should be allowed for anonymous users.
:DEFAULT: false */
enableForAnonymousUser: ?boolean;

View File

@@ -140,6 +140,38 @@ export class FilesRouter {
return;
}
const fileExtensions = config.fileUpload?.fileExtensions;
if (!isMaster && fileExtensions) {
const isValidExtension = extension => {
return fileExtensions.some(ext => {
if (ext === '*') {
return true;
}
const regex = new RegExp(fileExtensions);
if (regex.test(extension)) {
return true;
}
});
};
let extension = contentType;
if (filename && filename.includes('.')) {
extension = filename.split('.')[1];
} else if (contentType && contentType.includes('/')) {
extension = contentType.split('/')[1];
}
extension = extension.split(' ').join('');
if (!isValidExtension(extension)) {
next(
new Parse.Error(
Parse.Error.FILE_SAVE_ERROR,
`File upload of extension ${extension} is disabled.`
)
);
return;
}
}
const base64 = req.body.toString('base64');
const file = new Parse.File(filename, { base64 }, contentType);
const { metadata = {}, tags = {} } = req.fileData || {};