CWE-95, Improper Neutralization of Directives in Dynamically Evaluated Code (Eval Injection), is a narrower cousin of general code injection. The product takes input from an upstream component and uses it in a dynamic evaluation call such as eval without neutralizing the code syntax in it.
MITRE lists Java, JavaScript, Python, Perl, PHP and Ruby. Anywhere a language can run a string as code, this weakness is available.
How it happens
A developer wants flexibility: let the user type a formula, a filter, a config value, a sort key. The quick route is to hand that string to the language's evaluator.
// Vulnerable: the query string is executed as JavaScript
app.get("/filter", (req, res) => {
const rule = req.query.rule; // e.g. "price > 10"
const matches = items.filter(item => eval("item." + rule));
res.json(matches);
});
// rule = "constructor.constructor('return process')().exit()"
The developer pictured a comparison. The evaluator runs whatever it receives, and nothing in the language separates your logic from the attacker's.
The same shape appears in Python.
# Vulnerable
value = eval(request.args["expr"])
Not all cases are this obvious. Two of the MITRE examples are chains where a different bug opens the door to the eval:
- CVE-2021-22204: a regex in EXIF processing code does not correctly determine where a string ends, enabling eval injection. MITRE notes it was exploited in the wild per CISA KEV.
- CVE-2021-22205: a backslash followed by a newline can bypass a validation step, leading to eval injection, also exploited in the wild per CISA KEV.
These show why a validation step in front of eval is a weak defense. In both cases the check existed and was still bypassed.
Also listed are CVE-2024-4181, where a framework for LLM applications allows eval injection through a crafted response from a hosting provider, and CVE-2022-2054, where a Python compiler uses eval() to execute malicious strings.
What an attacker gains
From MITRE's consequences:
- Read files and application data. Injected code can reach restricted data and files.
- Bypass authentication. In some cases injectable code controls it, which can make the flaw remote.
- Gain privileges. Injected code reaches resources the attacker cannot access directly.
- Loss of integrity, confidentiality and availability. MITRE notes integrity is affected in nearly all cases and the injection can often lead to execution of arbitrary code.
- Hide activities. Actions by injected code are often unlogged.
How to find it
SAST tools can find some instances by following data flow into evaluation calls, per MITRE. A good manual start is to search for eval(, exec(, new Function(, setTimeout with a string argument, instance_eval, PHP's eval, and Perl string evals.
For each match, trace the argument backward. Include non obvious sources: HTTP headers, file metadata such as EXIF, values read from a database that other users can write, and responses from other services.
Library code is part of the picture. A library that evals input can make your application vulnerable even when your own code is clean. A free dependency scan at gold.safeguard.sh/scan runs in your browser with no login and covers up to 100 dependencies, and your file stays on your machine.
How to fix it
MITRE's guidance, in practical order:
- Do not use eval at all, if possible. Refactor so that the feature does not need it.
- Use a safer parser for the data you expect. MITRE specifically points Python programmers to
ast.literal_eval(), which is intentionally designed to avoid executing code. For JSON, use a JSON parser. - Validate with an allowlist. Assume all input is malicious, accept only known good input, and check type, length, range and syntax.
- Canonicalize before validating. Decode input to its internal form first and make sure nothing is decoded twice. The CVE-2021-22205 example above is the cautionary tale: a validation step that the input slipped past.
Fixed versions of the earlier examples:
import ast
value = ast.literal_eval(request.args["expr"]) # literals only, no calls
// Use a fixed table of allowed comparisons instead of evaluating text
const ALLOWED = { gt: (a, b) => a > b, lt: (a, b) => a < b };
app.get("/filter", (req, res) => {
const cmp = ALLOWED[req.query.op];
if (!cmp) return res.status(400).end();
const limit = Number(req.query.value);
res.json(items.filter(item => cmp(item.price, limit)));
});
The user picks from a menu you wrote. They never write code.
Quick check
Why is a validation step in front of eval a fragile defense?
Two MITRE examples (CVE-2021-22204 and CVE-2021-22205) were chains where a parsing or validation flaw let crafted input through to the eval. Removing the eval removes the whole class.
What does MITRE suggest instead of eval in Python?
ast.literal_eval(), which is designed not to execute arbitrary code.