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

Thanks to Sho Sugiyama and xet7 !
This commit is contained in:
Lauri Ojansivu 2025-10-11 04:47:17 +03:00
parent 746eecf3d8
commit 81c3dc1d95
3 changed files with 134 additions and 59 deletions

View file

@ -51,15 +51,38 @@ 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 (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') { if (process.env.DEBUG === 'true') {
console.warn('Blocked potentially malicious SVG image:', src); console.warn('Blocked potentially malicious SVG image:', src);
} }
return false; 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;
}
}
}
} }
// Block elements with dangerous attributes // Block elements with dangerous attributes

View file

@ -51,15 +51,38 @@ 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 (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') { if (process.env.DEBUG === 'true') {
console.warn('Blocked potentially malicious SVG image:', src); console.warn('Blocked potentially malicious SVG image:', src);
} }
return false; 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;
}
}
}
} }
// Block elements with dangerous attributes // Block elements with dangerous attributes

View file

@ -103,19 +103,47 @@ 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 && ( if (content) {
content.includes('<svg') || // Check for SVG content
const hasSVG = content.includes('<svg') ||
content.includes('data:image/svg') || content.includes('data:image/svg') ||
content.includes('xlink:href') || content.includes('xlink:href') ||
content.includes('<use') || content.includes('<use') ||
content.includes('<defs>') 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') { if (process.env.DEBUG === 'true') {
console.warn('Blocked potentially malicious SVG content in HTML:', content.substring(0, 100) + '...'); 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 // Replace with warning
token.type = 'paragraph_open'; token.type = 'paragraph_open';
token.tag = 'p'; token.tag = 'p';
@ -158,6 +186,7 @@ Markdown.use(function(md) {
} }
} }
} }
}
}); });
}); });