Safeguard
Application Security•

CWE-95 Eval Injection: Why eval() on Input Goes Wrong

Eval injection is code injection through eval and similar calls. Learn how CWE-95 works, the CVEs MITRE cites, and safer replacements.

Safeguard Security Team
4 min read

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:

  1. Do not use eval at all, if possible. Refactor so that the feature does not need it.
  2. 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.
  3. Validate with an allowlist. Assume all input is malicious, accept only known good input, and check type, length, range and syntax.
  4. 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.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.