fix: dont allow reading attributes of unsafe objects (#18706)

This commit is contained in:
Ankush Menat 2022-11-06 17:33:02 +05:30 committed by GitHub
parent 09d35c74eb
commit e02b90cd5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 4 deletions

View file

@ -1,3 +1,5 @@
import types
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils.safe_exec import get_safe_globals, safe_exec
@ -59,3 +61,17 @@ class TestSafeExec(FrappeTestCase):
# enqueue whitelisted method
safe_exec("""frappe.enqueue("ping", now=True)""")
def test_ensure_getattrable_globals(self):
def check_safe(objects):
for obj in objects:
if isinstance(obj, (types.ModuleType, types.CodeType, types.TracebackType, types.FrameType)):
self.fail(f"{obj} wont work in safe exec.")
elif isinstance(obj, dict):
check_safe(obj.values())
check_safe(get_safe_globals().values())
def test_unsafe_objects(self):
unsafe_global = {"frappe": frappe}
self.assertRaises(SyntaxError, safe_exec, """frappe.msgprint("Hello")""", unsafe_global)

View file

@ -2,6 +2,9 @@ import copy
import inspect
import json
import mimetypes
import types
from contextlib import contextmanager
from functools import lru_cache
import RestrictedPython.Guards
from RestrictedPython import compile_restricted, safe_globals
@ -64,14 +67,20 @@ def safe_exec(script, _globals=None, _locals=None, restrict_commit_rollback=Fals
exec_globals.frappe.db.pop("rollback", None)
exec_globals.frappe.db.pop("add_index", None)
# execute script compiled by RestrictedPython
frappe.flags.in_safe_exec = True
exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used
frappe.flags.in_safe_exec = False
with safe_exec_flags(), patched_qb():
# execute script compiled by RestrictedPython
exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used
return exec_globals, _locals
@contextmanager
def safe_exec_flags():
frappe.flags.in_safe_exec = True
yield
frappe.flags.in_safe_exec = False
def get_safe_globals():
datautils = frappe._dict()
@ -258,6 +267,24 @@ def call_with_form_dict(function, kwargs):
frappe.local.form_dict = form_dict
@contextmanager
def patched_qb():
try:
_terms = frappe.qb.terms
frappe.qb.terms = _flatten(frappe.qb.terms)
yield
finally:
frappe.qb.terms = _terms
@lru_cache
def _flatten(module):
new_mod = NamespaceDict()
for name, obj in inspect.getmembers(module, lambda x: not inspect.ismodule(x)):
new_mod[name] = obj
return new_mod
def get_python_builtins():
return {
"abs": abs,
@ -350,6 +377,10 @@ def _getattr(object, name, default=None):
if isinstance(name, str) and (name in UNSAFE_ATTRIBUTES):
raise SyntaxError(f"{name} is an unsafe attribute")
if isinstance(object, (types.ModuleType, types.CodeType, types.TracebackType, types.FrameType)):
raise SyntaxError(f"Reading {object} attributes is not allowed")
return RestrictedPython.Guards.safer_getattr(object, name, default=default)