mirror of
https://github.com/wekan/wekan.git
synced 2025-12-18 00:10:13 +01:00
Security Fix JVN#15385465: CWE-79 XSS, that affected WeKan 7.94.
Thanks to Sho Sugiyama and xet7 !
This commit is contained in:
parent
746eecf3d8
commit
81c3dc1d95
3 changed files with 134 additions and 59 deletions
|
|
@ -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 (src) {
|
||||||
if (process.env.DEBUG === 'true') {
|
// Block all SVG data URIs to prevent XSS via embedded JavaScript
|
||||||
console.warn('Blocked potentially malicious SVG image:', src);
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 (src) {
|
||||||
if (process.env.DEBUG === 'true') {
|
// Block all SVG data URIs to prevent XSS via embedded JavaScript
|
||||||
console.warn('Blocked potentially malicious SVG image:', src);
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 && (
|
if (content) {
|
||||||
content.includes('<svg') ||
|
// Check for SVG content
|
||||||
content.includes('data:image/svg') ||
|
const hasSVG = content.includes('<svg') ||
|
||||||
content.includes('xlink:href') ||
|
content.includes('data:image/svg') ||
|
||||||
content.includes('<use') ||
|
content.includes('xlink:href') ||
|
||||||
content.includes('<defs>')
|
content.includes('<use') ||
|
||||||
)) {
|
content.includes('<defs>');
|
||||||
if (process.env.DEBUG === 'true') {
|
|
||||||
console.warn('Blocked potentially malicious SVG content in HTML:', content.substring(0, 100) + '...');
|
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue