CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')
Overview
Eval injection happens when untrusted input reaches a dynamic code execution function such as eval, exec or Function(). The interpreter reads the injected text as program source rather than as data, so the attacker's code runs inside the application process with the application's privileges.
Relationship to Other CWEs
- CWE-94 (Code Injection) - the broader category covering all forms of dynamic code generation.
- CWE-95 (this page) - code injection through
eval-style functions specifically.
OWASP Classification
A05:2025 - Injection
Risk
Critical: Injected code runs inside the application process, so whatever that process can read, write or reach is available to the attacker. Sandboxing and process isolation bound that reach; only removing the dynamic execution closes it.
Remediation Steps
Core Principle: Never execute dynamically generated code derived from untrusted input; eliminate eval/exec-style evaluation and replace it with safe parsers or allowlisted interpreters.
Trace the Data Path
Work out how untrusted data reaches a code execution call:
- Source: where untrusted data enters (user input, external files, databases, network requests, configuration)
- Data Flow: how that data moves through the application on its way to the sink
- Sink: the code execution function (
eval(),exec(),Function(),compile(),ScriptEngine) - Missing Protection: no validation between the two, or a dangerous function used where a safe alternative exists
Eliminate Dynamic Code Execution (Primary Defense)
Do not pass untrusted data to eval() or anything like it. Refactor so the call is gone rather than guarded; the replacements are in the next section.
Functions to remove, by language:
- Python:
eval(),exec(),compile(),__import__() - JavaScript:
eval(),new Function(), browsersetTimeout(string),node:vmused as a security boundary - PHP:
eval(), and variable functions such as$fn()orcall_user_func()with an untrusted name.create_function()and the string-argument form ofassert()were removed in PHP 8.0, so they only matter when triaging a PHP 7 codebase - Java:
ScriptEngine.eval(), reflection with untrusted input
Why this works: Removing dynamic code execution removes the code-injection sink from that input path.
Use Safe Alternatives
For mathematical expressions:
- Use a maintained expression evaluator (math.js) with its own parser entry points disabled, and check the release and advisory history before adopting one -
expr-evalis still widely recommended and has been unreleased since 2019 with two open high-severity advisories - Parse to AST and allowlist operators
- Use operator mapping (map "+" to addition function)
For configuration and logic:
- JSON/YAML configuration files
- Rule engines (Drools, Easy Rules)
- Strategy pattern with pre-defined implementations
- Template engines with auto-escaping
For dynamic dispatch:
- Switch/case statements with enumerated options
- Command pattern with registered handlers
- Plugin systems with sandboxing
If Execution Unavoidable: Sandbox and Restrict
Sandboxing is a defense-in-depth measure, not a fix: it bounds what injected code can reach, it does not stop it running. If dynamic execution truly cannot be removed:
- Isolate it: a separate locked-down process or container with no network or filesystem access, a RestrictedPython-style constrained interpreter, or a GraalVM context with current security support and strict host-access controls
- Apply strict timeouts, measured in milliseconds rather than seconds, to limit denial of service
- Set memory limits to restrict resource consumption
- Allowlist operations so only specific functions and modules are reachable
Add Input Validation (Defense in Depth Only)
If sandboxing is used, add validation in front of it:
- Length limits, to limit denial of service
- A character allowlist: alphanumeric plus the specific characters the feature needs
- A denylist of dangerous keywords such as
eval,exec,importandsystem - AST parsing, to detect dangerous constructs before execution
Validation on its own is not a fix; bypasses are common. Use it alongside sandboxing, never in its place.
Test Thoroughly
Verify the fix with attack payloads:
- Code injection:
__import__('os').system('whoami'),eval('malicious') - Object access:
constructor.constructor('return process')() - Import injection:
require('child_process').exec('ls') - Encoding bypasses:
\x5f\x5fimport\x5f\x5f, Base64-encoded payloads - Confirm legitimate functionality still works
- Re-scan with the security scanner to confirm the finding is resolved
Common Vulnerable Patterns
- Passing untrusted input to
eval,exec, or similar functions - Using dynamic code execution for configuration or logic
Secure Patterns
Use Explicit Operation Mapping
Replace dynamic code execution with predefined operations:
- Define an allowlist of safe operations
- Map untrusted input to pre-defined functions
- Validate all inputs before execution
- Never execute user-provided code strings
Safe Expression Evaluation
For mathematical or logical expressions:
- Use AST (Abstract Syntax Tree) parsing
- Implement a custom parser with operator allowlist
- Use well-tested libraries designed for safe evaluation
- Reject any unexpected operations or syntax
Controlled Plugin/Module Loading
For dynamic functionality:
- Maintain an explicit allowlist of approved modules
- Map untrusted input to allowlisted paths only
- Validate plugin interfaces before execution
- Never use untrusted input directly in import/require statements
Common Pitfalls
- Falling back to a filtered
eval()for the one case a literal-only parser can't handle: A literal-only parser rejects function calls, attribute access and name lookups, which is exactly why it is safe. When a feature needs one more capability, such as calling a specific allowed function or reading an object property, the path of least resistance is a regex- or denylist-filteredeval()"just for this case". That reintroduces the fulleval()attack surface for the sake of one feature. - Allowlisted evaluators that resolve user-supplied names dynamically: An allowlisted expression evaluator is only as safe as its allowlist. If the allowlist is populated by mapping a user-supplied string to an arbitrary attribute lookup or dynamic-resolution call, rather than being a fixed, hardcoded set of functions, an attacker can still reach unintended methods through the resolution step.
- Timeout without eliminating the execution: Adding a short timeout or size limit around an
eval()-style call reduces resource-exhaustion risk but does nothing to prevent an attacker from reading files, opening sockets, or reading secrets in the time before the timeout fires. - Trusting an expression library's default configuration: Adopting a math or expression-parsing library instead of
eval()is a good move, but its default configuration may still expose functions such as dynamic imports, unit creation or symbolic simplification, leaving a reduced but real dynamic-execution surface open.
Language-Specific Guidance
Concrete APIs, secure examples, and the traps in the fix itself, for each language:
- Java - Safe alternatives to ScriptEngine, SpEL, OGNL, reflection and Groovy evaluation
- JavaScript/Node.js - Formula parsers instead of eval, operation mapping, controlled module loading, and CSP as a browser-level backstop
- Python - AST-based expression evaluation, operator mapping, safe configuration parsing, and alternatives to eval/exec/pickle