Cross-Site Scripting (XSS) from Untrusted Input Evaluated by eval() in Browser

Critical Risk Cross-Site Scripting
JavaScriptBrowserXSSCode InjectionevalClient-Side Security

What it is

Using eval() to execute dynamic strings containing user-influenced data creates severe security vulnerabilities, allowing attackers to execute arbitrary JavaScript code in the browser context, steal sensitive data, manipulate the DOM, and perform actions on behalf of the user.

// Example 1: Direct eval of URL parameter
function processUrlParameter() {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    
    if (code) {
        // Vulnerable: Direct eval of user input
        // Attacker can use: ?code=alert('XSS')
        eval(code);
    }
}

// Example 2: Dynamic function calls
function callFunction(funcName, args) {
    // Vulnerable: Building executable code from user input
    // Attacker can use: funcName="alert('XSS');console.log"
    const code = funcName + '(' + JSON.stringify(args) + ')';
    return eval(code);
}

// Example 3: Configuration evaluation
function applySettings(settingsString) {
    // Vulnerable: Evaluating user-provided configuration
    // Attacker can inject: "alert('XSS'); window.validSetting = true;"
    eval(settingsString);
}

// Example 4: Template processing
function renderTemplate(template, data) {
    // Vulnerable: Using eval for template interpolation
    return template.replace(/\{\{(.+?)\}\}/g, function(match, expression) {
        // expression could be "alert('XSS')"
        return eval(expression);
    });
}

// Example 5: JSONP-style callback
function handleCallback(callbackName, data) {
    // Vulnerable: Dynamic callback execution
    // Attacker can use callbackName="alert('XSS');window.callback"
    const code = callbackName + '(' + JSON.stringify(data) + ')';
    eval(code);
}

// Example 6: Math expression evaluator
function calculateExpression(expression) {
    // Vulnerable: Evaluating math expressions
    // Attacker can inject: "1+1; alert('XSS'); 1"
    try {
        return eval(expression);
    } catch (e) {
        return 'Error';
    }
}

// Example 7: Dynamic property access
class UserSettings {
    constructor() {
        this.theme = 'light';
        this.language = 'en';
    }
    
    getSetting(path) {
        // Vulnerable: Dynamic property access
        // Attacker can use: "constructor.constructor('alert(1)')"
        return eval('this.' + path);
    }
}
// Example 1: Safe URL parameter processing
function processUrlParameter() {
    const params = new URLSearchParams(window.location.search);
    const action = params.get('action');
    
    // Secure: Whitelist allowed actions
    const allowedActions = {
        'showHelp': () => displayHelp(),
        'refreshData': () => loadData(),
        'toggleTheme': () => switchTheme()
    };
    
    if (action && allowedActions[action]) {
        allowedActions[action]();
    }
}

// Example 2: Safe function calls with whitelist
function callFunction(funcName, args) {
    // Secure: Predefined function whitelist
    const allowedFunctions = {
        'calculateTotal': (items) => items.reduce((sum, item) => sum + (item.price || 0), 0),
        'formatDate': (date) => new Date(date).toLocaleDateString(),
        'validateEmail': (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
    };
    
    const func = allowedFunctions[funcName];
    if (!func || typeof func !== 'function') {
        throw new Error('Function not allowed: ' + funcName);
    }
    
    // Validate arguments
    if (!Array.isArray(args)) {
        throw new Error('Invalid arguments');
    }
    
    try {
        return func(...args);
    } catch (error) {
        console.error('Function execution error:', error);
        throw new Error('Function execution failed');
    }
}

// Example 3: Safe configuration processing
function applySettings(settingsString) {
    try {
        // Secure: Use JSON.parse instead of eval
        const settings = JSON.parse(settingsString);
        
        // Validate and apply settings safely
        const allowedSettings = ['theme', 'language', 'fontSize', 'autoSave'];
        
        for (const [key, value] of Object.entries(settings)) {
            if (allowedSettings.includes(key)) {
                applySetting(key, value);
            } else {
                console.warn('Unknown setting ignored:', key);
            }
        }
    } catch (error) {
        console.error('Invalid settings format:', error);
        throw new Error('Settings must be valid JSON');
    }
}

function applySetting(key, value) {
    switch (key) {
        case 'theme':
            if (['light', 'dark'].includes(value)) {
                document.body.className = 'theme-' + value;
            }
            break;
        case 'language':
            if (/^[a-z]{2}$/.test(value)) {
                setLanguage(value);
            }
            break;
        case 'fontSize':
            if (typeof value === 'number' && value >= 10 && value <= 24) {
                document.documentElement.style.fontSize = value + 'px';
            }
            break;
        case 'autoSave':
            if (typeof value === 'boolean') {
                setAutoSave(value);
            }
            break;
    }
}

// Example 4: Safe template processing
function renderTemplate(template, data) {
    // Secure: Safe template interpolation
    return template.replace(/\{\{([a-zA-Z_$][a-zA-Z0-9_$.]*)}\}/g, function(match, path) {
        const value = getNestedValue(data, path);
        return value !== undefined ? escapeHtml(String(value)) : '';
    });
}

function getNestedValue(obj, path) {
    const keys = path.split('.').filter(key => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key));
    
    return keys.reduce((current, key) => {
        return current && typeof current === 'object' ? current[key] : undefined;
    }, obj);
}

function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// Example 5: Safe callback handling
function handleCallback(callbackName, data) {
    // Secure: Whitelist allowed callbacks
    const allowedCallbacks = {
        'onDataLoaded': (data) => updateUI(data),
        'onError': (error) => showError(error),
        'onSuccess': (message) => showSuccess(message)
    };
    
    const callback = allowedCallbacks[callbackName];
    if (!callback) {
        console.warn('Unknown callback:', callbackName);
        return;
    }
    
    // Validate data before calling
    if (data && typeof data === 'object') {
        callback(sanitizeData(data));
    }
}

// Example 6: Safe math expression evaluator
function calculateExpression(expression) {
    // Secure: Whitelist allowed operations
    const allowedChars = /^[0-9+\-*/(). ]+$/;
    
    if (!allowedChars.test(expression)) {
        throw new Error('Invalid characters in expression');
    }
    
    // Additional validation: no consecutive operators, balanced parentheses
    if (!isValidMathExpression(expression)) {
        throw new Error('Invalid math expression');
    }
    
    try {
        // Use Function constructor with restricted scope (still better than eval)
        // Or better yet, implement a proper math parser
        return evaluateMathExpression(expression);
    } catch (error) {
        throw new Error('Calculation error');
    }
}

function isValidMathExpression(expr) {
    // Check for balanced parentheses
    let balance = 0;
    for (const char of expr) {
        if (char === '(') balance++;
        if (char === ')') balance--;
        if (balance < 0) return false;
    }
    return balance === 0;
}

// Example 7: Safe property access
class UserSettings {
    constructor() {
        this.theme = 'light';
        this.language = 'en';
        this.fontSize = 14;
        this.autoSave = true;
    }
    
    getSetting(key) {
        // Secure: Direct property access with validation
        const allowedSettings = ['theme', 'language', 'fontSize', 'autoSave'];
        
        if (!allowedSettings.includes(key)) {
            throw new Error('Setting not found: ' + key);
        }
        
        return this[key];
    }
    
    setSetting(key, value) {
        const allowedSettings = ['theme', 'language', 'fontSize', 'autoSave'];
        
        if (!allowedSettings.includes(key)) {
            throw new Error('Setting not allowed: ' + key);
        }
        
        // Validate value based on setting type
        switch (key) {
            case 'theme':
                if (['light', 'dark'].includes(value)) {
                    this.theme = value;
                }
                break;
            case 'language':
                if (typeof value === 'string' && /^[a-z]{2}$/.test(value)) {
                    this.language = value;
                }
                break;
            case 'fontSize':
                if (typeof value === 'number' && value >= 10 && value <= 24) {
                    this.fontSize = value;
                }
                break;
            case 'autoSave':
                if (typeof value === 'boolean') {
                    this.autoSave = value;
                }
                break;
        }
    }
}

💡 Why This Fix Works

The vulnerable examples show common patterns where eval() is used unsafely with user input. The secure versions use whitelisting, proper validation, and safe alternatives to eliminate code injection risks.

Why it happens

Applications that pass user-controlled data directly to eval() without any validation or sanitization.

Root causes

Direct eval() of User Input

Applications that pass user-controlled data directly to eval() without any validation or sanitization.

Preview example – JAVASCRIPT
// User input from URL parameter or form
const userCode = new URLSearchParams(window.location.search).get('code');

// Vulnerable: Direct eval of user input
if (userCode) {
    eval(userCode); // Attacker can execute arbitrary JavaScript
}

// Another vulnerable pattern
function executeUserFunction(functionName, params) {
    // Vulnerable: Building code string with user input
    const code = functionName + '(' + JSON.stringify(params) + ')';
    return eval(code);
}

Dynamic Property Access with eval()

Using eval() to dynamically access object properties or execute methods based on user input.

Preview example – JAVASCRIPT
// Vulnerable: Dynamic property access
function getObjectProperty(obj, propertyPath) {
    // propertyPath could be "constructor.constructor('alert(1)')()"
    return eval('obj.' + propertyPath);
}

// Vulnerable: Dynamic method execution
function callMethod(objectName, methodName, args) {
    const code = objectName + '.' + methodName + '(' + args + ')';
    return eval(code); // Allows code injection
}

Template Processing with eval()

Using eval() to process templates or configuration strings that contain user input.

Preview example – JAVASCRIPT
// Vulnerable: Template processing
function processTemplate(template, data) {
    // Template might contain: "<script>alert('${data.userInput}')</script>"
    let processed = template;
    for (let key in data) {
        // This allows code injection if data contains malicious content
        processed = processed.replace('${' + key + '}', eval(data[key]));
    }
    return processed;
}

// Vulnerable: Configuration evaluation
function applyConfig(configString) {
    // configString could be: "alert('XSS'); validConfig = true;"
    eval(configString);
}

Fixes

1

Replace eval() with Safe Alternatives

Use safe alternatives like JSON.parse(), bracket notation, or function maps instead of eval().

2

Implement Safe Template Engine

Use a proper template engine with automatic escaping or implement safe template processing without eval().

3

Implement Content Security Policy

Use Content Security Policy to prevent eval() and other unsafe code execution as a defense-in-depth measure.

4

Use Safe JSON Processing

Replace eval() used for JSON processing with proper JSON.parse() with validation.

Detect This Vulnerability in Your Code

Sourcery automatically identifies cross-site scripting (xss) from untrusted input evaluated by eval() in browser and many other security issues in your codebase.