Описание
asteval has a Sandbox Escape via BaseException Subclasses
Summary
An attacker who can supply expressions to asteval.Interpreter.eval() can raise SystemExit,
KeyboardInterrupt, GeneratorExit, or BaseException from inside the sandbox. These
exceptions are subclasses of BaseException but not Exception, so they bypass the
except Exception: safety net in both run() and eval(). The exception propagates
verbatim to the calling application, terminating the process or disrupting signal and
cleanup handlers.
This is distinct from prior vulnerabilities CVE-2025-24359 (format string injection) and GHSA-vp47-9734-prjw (AST mutation TOCTOU), both fixed in 1.0.6. This vector is present in all versions including 1.0.6 and current HEAD.
Affected Code
asteval/astutils.py, lines 89–108 — FROM_PY exposes dangerous classes to sandbox users:
asteval/asteval.py, line 322 — run() exception handler:
asteval/asteval.py, line 370 — eval() exception handler:
asteval/asteval.py, line 264 — raise_exception() raises the class directly:
Root Cause
Python's exception hierarchy has two distinct branches under BaseException:
FROM_PY exposes all four non-Exception classes to sandbox users. When a user writes
raise SystemExit("msg"), the on_raise() handler calls:
which executes raise SystemExit(msg). This propagates through both except Exception:
guards unchecked and surfaces in the calling application.
Proof of Concept
Output (tested on asteval 1.0.6, Python 3.11/3.12):
Real-world server scenario
Impact
| Variant | Impact |
|---|---|
SystemExit | Process terminates; exit code and message attacker-controlled |
KeyboardInterrupt | Disrupts finally blocks, signal handlers, and KeyboardInterrupt-aware loops |
GeneratorExit | Disrupts generator cleanup in calling code |
BaseException | Generic escape, same propagation |
Any application that:
- Accepts user-supplied expressions via
asteval - Relies on
except Exception:at the top level (standard practice) - Does not wrap
aeval.eval()inexcept BaseException:(non-standard, unexpected requirement)
...is vulnerable to attacker-triggered process termination (DoS).
CVSS breakdown: Network-reachable (AV:N), no special conditions (AC:L), no credentials (PR:N), no interaction (UI:N), scope unchanged (S:U), no confidentiality/integrity impact (C:N/I:N), high availability impact — process termination (A:H).
Additional Note: File Read Capability (Acknowledged Limitation)
Independently of this vulnerability, asteval exposes a read-only open() wrapper
(_open in astutils.py) that allows reading arbitrary files with the permissions of the
calling process:
This is documented in doc/motivation.rst as a known design choice ("If reading from disk
must be forbidden, you will want to overwrite the open() function from the symbol table").
It is included here for completeness, not as a separate advisory claim.
Recommended Fix
Option A — Remove dangerous classes from FROM_PY (minimal, preferred):
Option B — Block non-Exception raises in on_raise():
Note: Option B also fixes a secondary bug on the same line — ' '.join(out.args) crashes
with TypeError when args contain non-strings (e.g., raise SystemExit(0) with integer
code). The fix uses str(a) for a in out.args.
Option C — Catch BaseException in run() and eval() (broadest, requires care):
Option A is the simplest and least likely to introduce regressions. Option B additionally
addresses the str.join crash on integer args.
Disclosure Timeline
| Date | Event |
|---|---|
| 2026-06-09 | Vulnerability discovered during code review |
| 2026-06-09 | Report submitted via GitHub Security Advisory |
| TBD | Maintainer acknowledgment |
| TBD + 90 days | Public disclosure deadline |
Researcher
Independent security researcher. No bug bounty program exists for this project. CVE assignment requested via GitHub Security Advisory submission.
References
- Prior CVE: CVE-2025-24359 (format string injection, fixed 1.0.6)
- Prior advisory: GHSA-vp47-9734-prjw (AST mutation TOCTOU, fixed 1.0.6)
- Python exception hierarchy: https://docs.python.org/3/library/exceptions.html#exception-hierarchy
astevaldocumentation: https://lmfit.github.io/asteval/
Пакеты
asteval
< 1.0.9
1.0.9
Связанные уязвимости
(ASTEVAL is an evaluator of Python expressions and statements. Prior to ...)
ASTEVAL is an evaluator of Python expressions and statements. Prior to 1.0.9, FROM_PY in asteval/astutils.py exposes BaseException, SystemExit, KeyboardInterrupt, and GeneratorExit to expressions evaluated by asteval.Interpreter.eval(), while run() and eval() in asteval/asteval.py catch Exception rather than these non-Exception BaseException subclasses. When an attacker-controlled expression raises one of these classes, on_raise() passes the class to raise_exception(), and the resulting exception bypasses the interpreter's safety handlers and propagates into the calling application. A consuming service that evaluates untrusted expressions can therefore be terminated or have signal and cleanup handling disrupted, causing denial of service. The separately documented read-only open() capability is not part of this vulnerability. This issue is fixed in version 1.0.9.
ASTEVAL is an evaluator of Python expressions and statements. Prior to ...