feat: Add new Parse Server option fileUpload.fileExtensions to restrict file upload by file extension; this fixes a security vulnerability in which a phishing attack could be performed using an uploaded HTML file; by default the new option only allows file extensions matching the regex pattern ^[^hH][^tT][^mM][^lL]?$, which excludes HTML files; this fix is released as a patch version given the severity of this vulnerability, however, if your app currently depends on uploading files with HTML file extensions then this may be a breaking change and you could allow HTML file upload by setting the option to ['.*'] (#8537)

This commit is contained in:
Manuel
2023-05-21 01:14:27 +02:00
committed by GitHub
parent e9ae4351a1
commit 196e05f047
6 changed files with 211 additions and 26 deletions

View File

@@ -424,6 +424,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 validateMasterKeyIps(masterKeyIps) {

View File

@@ -4,7 +4,6 @@ This code has been generated by resources/buildConfigDefinitions.js
Do not edit manually, but update Options/index.js
*/
var parsers = require('./parsers');
module.exports.SchemaOptions = {
afterMigration: {
env: 'PARSE_SERVER_SCHEMA_AFTER_MIGRATION',
@@ -880,6 +879,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

@@ -204,6 +204,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

@@ -496,6 +496,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

@@ -138,6 +138,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 || {};