Browse Source

Security Fix JVN#15385465: CWE-79 XSS, that affected WeKan 7.94.

Thanks to Sho Sugiyama and xet7 !
Lauri Ojansivu 4 days ago
parent
commit
81c3dc1d95

+ 28 - 5
imports/lib/secureDOMPurify.js

@@ -51,14 +51,37 @@ export function getSecureDOMPurifyConfig() {
           return false;
           return false;
         }
         }
 
 
-        // Block img tags with SVG data URIs
+        // Block img tags with SVG data URIs that could contain malicious JavaScript
         if (node.tagName && node.tagName.toLowerCase() === 'img') {
         if (node.tagName && node.tagName.toLowerCase() === 'img') {
           const src = node.getAttribute('src');
           const src = node.getAttribute('src');
-          if (src && (src.startsWith('data:image/svg') || src.endsWith('.svg'))) {
-            if (process.env.DEBUG === 'true') {
-              console.warn('Blocked potentially malicious SVG image:', src);
+          if (src) {
+            // Block all SVG data URIs to prevent XSS via embedded JavaScript
+            if (src.startsWith('data:image/svg') || src.endsWith('.svg')) {
+              if (process.env.DEBUG === 'true') {
+                console.warn('Blocked potentially malicious SVG image:', src);
+              }
+              return false;
+            }
+            
+            // Additional check for base64 encoded SVG with script tags
+            if (src.startsWith('data:image/svg+xml;base64,')) {
+              try {
+                const base64Content = src.split(',')[1];
+                const decodedContent = atob(base64Content);
+                if (decodedContent.includes('<script') || decodedContent.includes('javascript:')) {
+                  if (process.env.DEBUG === 'true') {
+                    console.warn('Blocked SVG with embedded JavaScript:', src.substring(0, 100) + '...');
+                  }
+                  return false;
+                }
+              } catch (e) {
+                // If decoding fails, block it as a safety measure
+                if (process.env.DEBUG === 'true') {
+                  console.warn('Blocked malformed SVG data URI:', src);
+                }
+                return false;
+              }
             }
             }
-            return false;
           }
           }
         }
         }
 
 

+ 28 - 5
packages/markdown/src/secureDOMPurify.js

@@ -51,14 +51,37 @@ export function getSecureDOMPurifyConfig() {
           return false;
           return false;
         }
         }
 
 
-        // Block img tags with SVG data URIs
+        // Block img tags with SVG data URIs that could contain malicious JavaScript
         if (node.tagName && node.tagName.toLowerCase() === 'img') {
         if (node.tagName && node.tagName.toLowerCase() === 'img') {
           const src = node.getAttribute('src');
           const src = node.getAttribute('src');
-          if (src && (src.startsWith('data:image/svg') || src.endsWith('.svg'))) {
-            if (process.env.DEBUG === 'true') {
-              console.warn('Blocked potentially malicious SVG image:', src);
+          if (src) {
+            // Block all SVG data URIs to prevent XSS via embedded JavaScript
+            if (src.startsWith('data:image/svg') || src.endsWith('.svg')) {
+              if (process.env.DEBUG === 'true') {
+                console.warn('Blocked potentially malicious SVG image:', src);
+              }
+              return false;
+            }
+            
+            // Additional check for base64 encoded SVG with script tags
+            if (src.startsWith('data:image/svg+xml;base64,')) {
+              try {
+                const base64Content = src.split(',')[1];
+                const decodedContent = atob(base64Content);
+                if (decodedContent.includes('<script') || decodedContent.includes('javascript:')) {
+                  if (process.env.DEBUG === 'true') {
+                    console.warn('Blocked SVG with embedded JavaScript:', src.substring(0, 100) + '...');
+                  }
+                  return false;
+                }
+              } catch (e) {
+                // If decoding fails, block it as a safety measure
+                if (process.env.DEBUG === 'true') {
+                  console.warn('Blocked malformed SVG data URI:', src);
+                }
+                return false;
+              }
             }
             }
-            return false;
           }
           }
         }
         }
 
 

+ 78 - 49
packages/markdown/src/template-integration.js

@@ -103,58 +103,87 @@ Markdown.use(function(md) {
         }
         }
       }
       }
 
 
-      // Check for HTML tokens that might contain SVG
+      // Check for HTML tokens that might contain SVG or malicious content
       if (token.type === 'html_block' || token.type === 'html_inline') {
       if (token.type === 'html_block' || token.type === 'html_inline') {
         const content = token.content;
         const content = token.content;
-        if (content && (
-          content.includes('<svg') ||
-          content.includes('data:image/svg') ||
-          content.includes('xlink:href') ||
-          content.includes('<use') ||
-          content.includes('<defs>')
-        )) {
-          if (process.env.DEBUG === 'true') {
-            console.warn('Blocked potentially malicious SVG content in HTML:', content.substring(0, 100) + '...');
+        if (content) {
+          // Check for SVG content
+          const hasSVG = content.includes('<svg') ||
+                        content.includes('data:image/svg') ||
+                        content.includes('xlink:href') ||
+                        content.includes('<use') ||
+                        content.includes('<defs>');
+          
+          // Check for malicious img tags with SVG data URIs
+          const hasMaliciousImg = content.includes('<img') && 
+                                 (content.includes('data:image/svg') || 
+                                  content.includes('src="data:image/svg'));
+          
+          // Check for base64 encoded SVG with script tags
+          const hasBase64SVG = content.includes('data:image/svg+xml;base64,');
+          
+          if (hasSVG || hasMaliciousImg || hasBase64SVG) {
+            if (process.env.DEBUG === 'true') {
+              console.warn('Blocked potentially malicious SVG content in HTML:', content.substring(0, 100) + '...');
+            }
+            
+            // Additional check for base64 encoded SVG with script tags
+            if (hasBase64SVG) {
+              try {
+                const base64Match = content.match(/data:image\/svg\+xml;base64,([^"'\s]+)/);
+                if (base64Match) {
+                  const decodedContent = atob(base64Match[1]);
+                  if (decodedContent.includes('<script') || decodedContent.includes('javascript:')) {
+                    if (process.env.DEBUG === 'true') {
+                      console.warn('Blocked SVG with embedded JavaScript in markdown');
+                    }
+                  }
+                }
+              } catch (e) {
+                // If decoding fails, continue with blocking
+              }
+            }
+            
+            // Replace with warning
+            token.type = 'paragraph_open';
+            token.tag = 'p';
+            token.nesting = 1;
+            token.attrSet('style', 'color: red; background: #ffe6e6; padding: 8px; border: 1px solid #ff9999;');
+            token.attrSet('title', 'Blocked potentially malicious SVG content');
+
+            // Add warning text
+            const warningToken = {
+              type: 'text',
+              content: '⚠️ Blocked potentially malicious SVG content for security reasons',
+              level: token.level,
+              markup: '',
+              info: '',
+              meta: null,
+              block: true,
+              hidden: false
+            };
+
+            // Insert warning token after the paragraph open
+            tokens.splice(i + 1, 0, warningToken);
+
+            // Add paragraph close token
+            const closeToken = {
+              type: 'paragraph_close',
+              tag: 'p',
+              nesting: -1,
+              level: token.level,
+              markup: '',
+              info: '',
+              meta: null,
+              block: true,
+              hidden: false
+            };
+            tokens.splice(i + 2, 0, closeToken);
+
+            // Remove the original HTML token
+            tokens.splice(i, 1);
+            i--; // Adjust index since we removed a token
           }
           }
-          // Replace with warning
-          token.type = 'paragraph_open';
-          token.tag = 'p';
-          token.nesting = 1;
-          token.attrSet('style', 'color: red; background: #ffe6e6; padding: 8px; border: 1px solid #ff9999;');
-          token.attrSet('title', 'Blocked potentially malicious SVG content');
-
-          // Add warning text
-          const warningToken = {
-            type: 'text',
-            content: '⚠️ Blocked potentially malicious SVG content for security reasons',
-            level: token.level,
-            markup: '',
-            info: '',
-            meta: null,
-            block: true,
-            hidden: false
-          };
-
-          // Insert warning token after the paragraph open
-          tokens.splice(i + 1, 0, warningToken);
-
-          // Add paragraph close token
-          const closeToken = {
-            type: 'paragraph_close',
-            tag: 'p',
-            nesting: -1,
-            level: token.level,
-            markup: '',
-            info: '',
-            meta: null,
-            block: true,
-            hidden: false
-          };
-          tokens.splice(i + 2, 0, closeToken);
-
-          // Remove the original HTML token
-          tokens.splice(i, 1);
-          i--; // Adjust index since we removed a token
         }
         }
       }
       }
     }
     }