Kaynağa Gözat

Security Fix JVN#74210258: Stored XSS.

Thanks to Ryoya Koyama of Mitsui Bussan Secure Directions, Inc and xet7 !
Lauri Ojansivu 6 gün önce
ebeveyn
işleme
e1fa607f87

+ 18 - 0
models/attachments.js

@@ -91,6 +91,24 @@ Attachments = new FilesCollection({
     const ret = fileStoreStrategyFactory.storagePath;
     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) {
     // current storage is the filesystem, update object and database
     Object.keys(fileObj.versions).forEach(versionName => {

+ 15 - 0
models/avatars.js

@@ -85,6 +85,21 @@ Avatars = new FilesCollection({
     return ret;
   },
   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/')) {
       return true;
     }

+ 15 - 1
models/lib/httpStream.js

@@ -17,12 +17,26 @@ export const httpStreamOutput = function(readStream, name, http, downloadFlag, c
     if (cacheControl) {
       http.response.setHeader('Cache-Control', cacheControl);
     }
+
+    // Set Content-Disposition header
     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 */
 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 dispositionName = `filename="${encodedName}"; filename=*UTF-8"${encodedName}";`;

+ 12 - 1
server/routes/legacyAttachments.js

@@ -53,7 +53,18 @@ if (Meteor.isServer) {
       // Set appropriate headers
       res.setHeader('Content-Type', attachment.type || 'application/octet-stream');
       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
       const fileStream = getOldAttachmentStream(attachmentId);