mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
Security Fix JVN#74210258: Stored XSS.
Thanks to Ryoya Koyama of Mitsui Bussan Secure Directions, Inc and xet7 !
This commit is contained in:
parent
2e91a359f5
commit
e1fa607f87
4 changed files with 60 additions and 2 deletions
|
|
@ -91,6 +91,24 @@ Attachments = new FilesCollection({
|
||||||
const ret = fileStoreStrategyFactory.storagePath;
|
const ret = fileStoreStrategyFactory.storagePath;
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
onBeforeUpload(file) {
|
||||||
|
// Block SVG files for attachments to prevent XSS attacks
|
||||||
|
if (file.name && file.name.toLowerCase().endsWith('.svg')) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.warn('Blocked SVG file upload for attachment:', file.name);
|
||||||
|
}
|
||||||
|
return 'SVG files are not allowed for attachments due to security reasons. Please use PNG, JPG, GIF, or other safe formats.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.type === 'image/svg+xml') {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.warn('Blocked SVG MIME type upload for attachment:', file.type);
|
||||||
|
}
|
||||||
|
return 'SVG files are not allowed for attachments due to security reasons. Please use PNG, JPG, GIF, or other safe formats.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
onAfterUpload(fileObj) {
|
onAfterUpload(fileObj) {
|
||||||
// current storage is the filesystem, update object and database
|
// current storage is the filesystem, update object and database
|
||||||
Object.keys(fileObj.versions).forEach(versionName => {
|
Object.keys(fileObj.versions).forEach(versionName => {
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,21 @@ Avatars = new FilesCollection({
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
onBeforeUpload(file) {
|
onBeforeUpload(file) {
|
||||||
|
// Block SVG files for avatars to prevent XSS attacks
|
||||||
|
if (file.name && file.name.toLowerCase().endsWith('.svg')) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.warn('Blocked SVG file upload for avatar:', file.name);
|
||||||
|
}
|
||||||
|
return 'SVG files are not allowed for avatars due to security reasons. Please use PNG, JPG, or GIF format.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.type === 'image/svg+xml') {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.warn('Blocked SVG MIME type upload for avatar:', file.type);
|
||||||
|
}
|
||||||
|
return 'SVG files are not allowed for avatars due to security reasons. Please use PNG, JPG, or GIF format.';
|
||||||
|
}
|
||||||
|
|
||||||
if (file.size <= avatarsUploadSize && file.type.startsWith('image/')) {
|
if (file.size <= avatarsUploadSize && file.type.startsWith('image/')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,26 @@ export const httpStreamOutput = function(readStream, name, http, downloadFlag, c
|
||||||
if (cacheControl) {
|
if (cacheControl) {
|
||||||
http.response.setHeader('Cache-Control', cacheControl);
|
http.response.setHeader('Cache-Control', cacheControl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Content-Disposition header
|
||||||
http.response.setHeader('Content-Disposition', getContentDisposition(name, http?.params?.query?.download));
|
http.response.setHeader('Content-Disposition', getContentDisposition(name, http?.params?.query?.download));
|
||||||
|
|
||||||
|
// Add security headers to prevent XSS attacks
|
||||||
|
const isSvgFile = name && name.toLowerCase().endsWith('.svg');
|
||||||
|
if (isSvgFile) {
|
||||||
|
// For SVG files, add strict CSP to prevent script execution
|
||||||
|
http.response.setHeader('Content-Security-Policy', "default-src 'none'; script-src 'none'; object-src 'none';");
|
||||||
|
http.response.setHeader('X-Content-Type-Options', 'nosniff');
|
||||||
|
http.response.setHeader('X-Frame-Options', 'DENY');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** will initiate download, if links are called with ?download="true" queryparam */
|
/** will initiate download, if links are called with ?download="true" queryparam */
|
||||||
const getContentDisposition = (name, downloadFlag) => {
|
const getContentDisposition = (name, downloadFlag) => {
|
||||||
const dispositionType = downloadFlag === 'true' ? 'attachment;' : 'inline;';
|
// Force attachment disposition for SVG files to prevent XSS attacks
|
||||||
|
const isSvgFile = name && name.toLowerCase().endsWith('.svg');
|
||||||
|
const forceAttachment = isSvgFile || downloadFlag === 'true';
|
||||||
|
const dispositionType = forceAttachment ? 'attachment;' : 'inline;';
|
||||||
|
|
||||||
const encodedName = encodeURIComponent(name);
|
const encodedName = encodeURIComponent(name);
|
||||||
const dispositionName = `filename="${encodedName}"; filename=*UTF-8"${encodedName}";`;
|
const dispositionName = `filename="${encodedName}"; filename=*UTF-8"${encodedName}";`;
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,18 @@ if (Meteor.isServer) {
|
||||||
// Set appropriate headers
|
// Set appropriate headers
|
||||||
res.setHeader('Content-Type', attachment.type || 'application/octet-stream');
|
res.setHeader('Content-Type', attachment.type || 'application/octet-stream');
|
||||||
res.setHeader('Content-Length', attachment.size || 0);
|
res.setHeader('Content-Length', attachment.size || 0);
|
||||||
res.setHeader('Content-Disposition', `attachment; filename="${attachment.name}"`);
|
|
||||||
|
// Force attachment disposition for SVG files to prevent XSS attacks
|
||||||
|
const isSvgFile = attachment.name && attachment.name.toLowerCase().endsWith('.svg');
|
||||||
|
const disposition = isSvgFile ? 'attachment' : 'attachment'; // Always use attachment for legacy files
|
||||||
|
res.setHeader('Content-Disposition', `${disposition}; filename="${attachment.name}"`);
|
||||||
|
|
||||||
|
// Add security headers for SVG files
|
||||||
|
if (isSvgFile) {
|
||||||
|
res.setHeader('Content-Security-Policy', "default-src 'none'; script-src 'none'; object-src 'none';");
|
||||||
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||||
|
res.setHeader('X-Frame-Options', 'DENY');
|
||||||
|
}
|
||||||
|
|
||||||
// Get GridFS stream for legacy attachment
|
// Get GridFS stream for legacy attachment
|
||||||
const fileStream = getOldAttachmentStream(attachmentId);
|
const fileStream = getOldAttachmentStream(attachmentId);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue